Skip to content

feat: add framework flag aliases and unified IM pagination - #2146

Open
liangshuo-1 wants to merge 1 commit into
mainfrom
feat/framework-flag-aliases
Open

feat: add framework flag aliases and unified IM pagination#2146
liangshuo-1 wants to merge 1 commit into
mainfrom
feat/framework-flag-aliases

Conversation

@liangshuo-1

@liangshuo-1 liangshuo-1 commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Add a framework-level contract for exact flag-name aliases and a shared, format-aware pagination pipeline for IM list commands. This replaces command-local compatibility plumbing and copied page loops while preserving each domain's ownership of true semantic conversions and keeping one-page behavior as the default.

Changes

  • Introduce declarative shortcut flag aliases with canonical precedence, caller-facing validation attribution, schema/manifest metadata, and lint coverage.
  • Keep semantic compatibility explicit within Mail, Slides, Base, Sheets, and IM instead of routing business meaning through framework normalization.
  • Add shared --page-all, --page-limit, and --page-delay contracts to IM chat, chat-search, chat-message, and thread-message lists.
  • Decode and accumulate pages before running enrichment/filtering and emit pagination state through format-appropriate metadata; JSON stays a single valid envelope and record-stream stdout remains clean.
  • Bound page walks, detect repeated cursors, preserve resumable page tokens, honor context cancellation, and throttle requests between pages.
  • Update command references and tests for aliases, validation, pagination completion, output formats, and compatibility behavior.

Impact

  • Canonical flags remain the documented interface; exact-name aliases behave consistently across shortcut domains.
  • Existing single-page calls keep their request and output behavior.
  • --page-all callers get consistent safety limits, resumability, and metadata across supported IM commands.

Test Plan

  • make unit-test
  • go vet ./...
  • gofmt -l . produces no output
  • go mod tidy leaves go.mod and go.sum unchanged
  • golangci-lint v2.1.6 run --new-from-rev=origin/main reports 0 issues
  • make build
  • Targeted dry-run E2E coverage passes for Base, IM, Mail, and Sheets alias/pagination flows
  • Manual local IM verification covers multi-page, limit/resume, filter, and JSON/table/CSV/NDJSON output paths

Related Issues

Summary by CodeRabbit

  • New Features
    • Added declarative flag aliases across shortcuts, including pagination, token, presentation, message ID, and search options.
    • Added automatic pagination for IM list and search commands with configurable page limits, delays, resume tokens, and progress metadata.
    • Added structured pagination information for JSON, table, pretty, CSV, and NDJSON output formats.
  • Bug Fixes
    • Improved alias conflict detection and canonical validation errors.
    • Large mute-status requests are now split into supported batches.
  • Documentation
    • Updated shortcut references with pagination, aliases, limits, and compatibility guidance.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds declarative flag aliases, shared shortcut normalization, automatic IM pagination, pagination-aware output metadata, manifest alias support, and flag-contract linting. It also updates shortcut implementations, tests, and documentation.

Changes

Flag aliases and contracts

Layer / File(s) Summary
Alias binding and normalization
internal/flagalias/*, shortcuts/common/*
Flags can declare aliases that resolve to one canonical flag. Binding validates collisions and preserves repeated-flag semantics.
Manifest and lint integration
internal/qualitygate/..., lint/...
Manifests export aliases as canonical metadata. Validation rejects alias conflicts. The new lint domain detects unsupported alias patterns.

Pagination and output

Layer / File(s) Summary
Shared pagination
shortcuts/common/page_all_flags.go, shortcuts/common/paginate_into.go, shortcuts/im/*
IM list commands support page limits, delays, cursor continuation, typed page merging, and pagination metadata.
Output metadata
internal/output/*
JSON uses pagination envelopes. Pretty and table formats render summaries. CSV and NDJSON emit structured diagnostics separately.

Shortcut migrations

Layer / File(s) Summary
Canonical shortcut aliases
shortcuts/base/*, shortcuts/mail/*, shortcuts/sheets/*, shortcuts/slides/*
Legacy hidden flags are replaced with aliases on canonical flags. Shortcut implementations read canonical runtime values.
Compatibility and validation tests
tests/cli_e2e/*, shortcuts/*/*_test.go, skills/*
Tests and documentation cover alias precedence, normalization, pagination behavior, output formats, and updated validation messages.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: feature

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.83% 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
Title check ✅ Passed The title clearly summarizes the framework flag-alias and unified IM pagination changes.
Description check ✅ Passed The description includes complete summary, changes, impact, test plan, and related issue sections with specific verification details.
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.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/framework-flag-aliases

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.

@github-actions github-actions Bot added domain/base PR touches the base domain domain/ccm PR touches the ccm domain domain/im PR touches the im domain domain/mail PR touches the mail domain size/XL Architecture-level or global-impact change labels Aug 2, 2026
Introduce declarative exact-name flag aliases at the shortcut framework boundary while keeping semantic compatibility domain-owned. Add a shared, format-aware IM pagination pipeline with consistent flags, metadata, safety bounds, resumable cursors, and request throttling.
@liangshuo-1
liangshuo-1 force-pushed the feat/framework-flag-aliases branch from a435d6f to e32d7cb Compare August 2, 2026 19:50
@liangshuo-1 liangshuo-1 changed the title refactor: unify shortcut aliases and IM pagination feat: add framework flag aliases and unified IM pagination Aug 2, 2026
@liangshuo-1
liangshuo-1 changed the base branch from feat/agent-affordance-fixes to main August 2, 2026 19:51
@liangshuo-1
liangshuo-1 marked this pull request as ready for review August 2, 2026 19:51
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

PR Quality Summary

CI did not complete successfully. Use the failed check links below to decide whether this PR needs a code change or a rerun.

Failed checks

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@e32d7cb42e8076b27081653f578b327b0bb5e911

🧩 Skill update

npx skills add larksuite/cli#feat/framework-flag-aliases -y -g

@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.46479% with 160 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.65%. Comparing base (40a0a9d) to head (e32d7cb).

Files with missing lines Patch % Lines
shortcuts/common/paginate_into.go 71.31% 26 Missing and 9 partials ⚠️
internal/flagalias/flagalias.go 65.62% 21 Missing and 12 partials ⚠️
shortcuts/common/flag_context.go 56.09% 15 Missing and 3 partials ⚠️
shortcuts/common/page_all_flags.go 66.03% 13 Missing and 5 partials ⚠️
internal/output/emitter.go 72.34% 9 Missing and 4 partials ⚠️
internal/qualitygate/manifest/schema.go 60.00% 5 Missing and 5 partials ⚠️
shortcuts/im/mute_filter.go 71.42% 6 Missing and 2 partials ⚠️
shortcuts/im/im_threads_messages_list.go 79.41% 4 Missing and 3 partials ⚠️
internal/output/emit.go 63.63% 2 Missing and 2 partials ⚠️
shortcuts/common/runner.go 60.00% 2 Missing and 2 partials ⚠️
... and 3 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2146      +/-   ##
==========================================
+ Coverage   75.57%   75.65%   +0.08%     
==========================================
  Files         931      938       +7     
  Lines       99162    99580     +418     
==========================================
+ Hits        74937    75340     +403     
+ Misses      18501    18476      -25     
- Partials     5724     5764      +40     

☔ View full report in Codecov by Harness.
📢 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (3)
lint/flagcontract/scan.go (1)

65-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

The alias-flag rule is a fail-open heuristic. Consider documenting the limits.

Three conditions must all hold for a violation to fire, and each one under-matches:

  • aliasDescription matches three fixed phrases. A Desc such as "deprecated name for --order" or "accepts the old spelling of --order" is not detected.
  • Line 120 recognizes only the bare identifier true. Hidden: isHidden or Hidden: someConst is not detected.
  • hiddenFlagLiteral inspects any composite literal that has Name, Desc, and Hidden keys. It does not confirm the literal is a common.Flag. The fixture at lint/flagcontract/scan_test.go lines 16-19 uses an anonymous struct and still triggers the rule.

Fail-open is a defensible choice for a new lint domain. The concern is the description in lint/README.md lines 46-48, which states the guard "rejects ... independent hidden flags described as aliases" without qualification. A maintainer may treat the lint as an authoritative gate when it is a best-effort signal.

Either narrow the literal check to common.Flag using type information, as the sibling domaincontract package does, or state the heuristic nature in the README.

Also applies to: 103-140

🤖 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 `@lint/flagcontract/scan.go` around lines 65 - 71, Update the flag-contract
lint documentation in lint/README.md to describe the alias-flag check as a
best-effort heuristic rather than an authoritative rejection, noting that it may
miss alternate alias descriptions or hidden-value expressions and may match
structurally similar non-common.Flag literals. Keep the existing detection
behavior unchanged.
shortcuts/im/im_chat_search.go (1)

322-333: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the named constant instead of the literal default.

params["page_size"] = 20 duplicates chatSearchDefaultPageSize (already used for the flag default and validation bound). Using the literal risks drift if the default ever changes.

♻️ Proposed fix
 	if n := runtime.Int("page-size"); n > 0 {
 		params["page_size"] = n
 	} else {
-		params["page_size"] = 20
+		params["page_size"] = chatSearchDefaultPageSize
 	}
🤖 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 `@shortcuts/im/im_chat_search.go` around lines 322 - 333, Replace the literal
fallback value in buildSearchChatParams with the existing
chatSearchDefaultPageSize constant, while preserving the current handling of
positive page-size values and page tokens.
shortcuts/im/im_chat_messages_list.go (1)

121-124: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Page size is validated twice on the execute path.

Execute validates page size at Line 122. buildChatMessageListRequest at Line 129 runs the same ValidatePageSizeTyped call at Line 242 and returns the same typed --page-size error. Remove the Line 122 call to keep one validation site.

♻️ Proposed cleanup
 	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
-		if _, err := common.ValidatePageSizeTyped(runtime, "page-size", chatMessagesListDefaultPageSize, 1, chatMessagesListMaxPageSize); err != nil {
-			return err
-		}
 		chatId, err := resolveChatIDForMessagesList(runtime, false)
🤖 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 `@shortcuts/im/im_chat_messages_list.go` around lines 121 - 124, Remove the
redundant ValidatePageSizeTyped call from the Execute function and let
buildChatMessageListRequest remain the single page-size validation site,
preserving its existing typed --page-size error handling.
🤖 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 `@shortcuts/common/paginate_into.go`:
- Around line 105-115: In the pagination flow around the maxPages check, return
successfully when pageNumber reaches policy.maxPages before validating
nextPageToken against seen. Keep requiring a non-empty token when another
request is allowed, and update the affected paginate_into tests to expect echoed
cursors to succeed for single-page reads.

In `@shortcuts/common/runner_normalize_test.go`:
- Line 121: Remove the ineffective normalizeCalled flag and its assertion from
the test, since the current flow never invokes Normalize through runShortcut.
Keep the direct contract assertions around ParseFlags and ValidateRequiredFlags
unchanged, or update the test to exercise runShortcut if ordering coverage is
required.
- Around line 38-40: Update the shortcut test setup around newTestFactory and
newTestShortcutCmd to use cmdutil.TestFactory(t, config) instead of constructing
an empty factory directly. Set LARKSUITE_CLI_CONFIG_DIR to t.TempDir() with
t.Setenv before creating the factory, while preserving the existing input stream
and command setup.
- Around line 79-81: Update the error assertion around runShortcut in the
normalization failure test to inspect errs.ProblemOf rather than only checking
for a non-nil error. Assert the expected category, subtype, and param values,
and verify the original cause is preserved, following the existing
typed-metadata assertions in this file’s test around lines 108-114.

In `@shortcuts/im/sort_flags.go`:
- Around line 58-67: Update the legacy-flag handling around flags.Str and
SetCanonicalFrom so an explicitly provided empty legacy value is not propagated
to the canonical flag. Validate it like other unrecognized values and attribute
the error to the legacy flag, or leave the canonical default unchanged; then
update the corresponding test case in sort_flags_test.go to reflect the
corrected behavior.

In `@skills/lark-im/SKILL.md`:
- Around line 109-117: Escape the literal pipe in the “asc|desc” text within the
+chat-messages-list and +threads-messages-list table entries so Markdown treats
it as cell content and preserves the intended table structure.

In `@tests/cli_e2e/base/base_limit_dryrun_test.go`:
- Around line 90-101: Strengthen
TestBaseListDryRunValidatesPageSizeAliasAsCanonicalLimit by asserting the
complete validation error contract: verify error.type is "validation",
error.subtype is "invalid_argument", and stdout is empty, while preserving the
existing error.param and message assertions.

In `@tests/cli_e2e/mail/mail_triage_dryrun_test.go`:
- Around line 45-70: Add a self-contained live E2E test alongside
TestMail_TriageDryRunUsesPageSizeAsExactMaxAlias that executes mail +triage with
the relevant --page-size/--max alias combinations against the configured test
mailbox, rather than inspecting dry-run request parameters. Assert the command
succeeds and verify the resulting triage behavior reflects the alias precedence
and exact page-size/max semantics.
- Around line 53-55: Update the test cases around the mail triage dry-run flag
parsing to preserve canonical --max precedence: when both --max and its
--page-size alias are supplied, expect the --max value regardless of argument
order. Change the “alias last” case to expect 7 while keeping the single-flag
and “canonical last” cases aligned with this behavior.

---

Nitpick comments:
In `@lint/flagcontract/scan.go`:
- Around line 65-71: Update the flag-contract lint documentation in
lint/README.md to describe the alias-flag check as a best-effort heuristic
rather than an authoritative rejection, noting that it may miss alternate alias
descriptions or hidden-value expressions and may match structurally similar
non-common.Flag literals. Keep the existing detection behavior unchanged.

In `@shortcuts/im/im_chat_messages_list.go`:
- Around line 121-124: Remove the redundant ValidatePageSizeTyped call from the
Execute function and let buildChatMessageListRequest remain the single page-size
validation site, preserving its existing typed --page-size error handling.

In `@shortcuts/im/im_chat_search.go`:
- Around line 322-333: Replace the literal fallback value in
buildSearchChatParams with the existing chatSearchDefaultPageSize constant,
while preserving the current handling of positive page-size values and page
tokens.
🪄 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 Plus

Run ID: a60d2b20-14e8-4749-b43d-ebb464649ed7

📥 Commits

Reviewing files that changed from the base of the PR and between 40a0a9d and e32d7cb.

📒 Files selected for processing (102)
  • internal/flagalias/flagalias.go
  • internal/flagalias/flagalias_test.go
  • internal/output/emit.go
  • internal/output/emitter.go
  • internal/output/emitter_contract_test.go
  • internal/output/emitter_legacy_compat_test.go
  • internal/output/envelope.go
  • internal/output/testdata/runtime_context_legacy.golden.json
  • internal/qualitygate/cmd/manifest-export/collect.go
  • internal/qualitygate/cmd/manifest-export/collect_alias_test.go
  • internal/qualitygate/cmd/manifest-export/main_test.go
  • internal/qualitygate/manifest/io_test.go
  • internal/qualitygate/manifest/schema.go
  • internal/qualitygate/rules/dryrun.go
  • internal/qualitygate/rules/refs.go
  • internal/qualitygate/rules/refs_test.go
  • lint/README.md
  • lint/flagcontract/scan.go
  • lint/flagcontract/scan_test.go
  • lint/main.go
  • shortcuts/base/base_dryrun_ops_test.go
  • shortcuts/base/base_execute_test.go
  • shortcuts/base/base_resolve.go
  • shortcuts/base/base_resolve_test.go
  • shortcuts/base/base_shortcut_helpers.go
  • shortcuts/base/base_shortcuts_test.go
  • shortcuts/base/field_list.go
  • shortcuts/base/field_ops.go
  • shortcuts/base/field_search_options.go
  • shortcuts/base/record_list.go
  • shortcuts/base/record_ops.go
  • shortcuts/base/record_query.go
  • shortcuts/base/record_search.go
  • shortcuts/base/table_list.go
  • shortcuts/base/table_ops.go
  • shortcuts/base/view_list.go
  • shortcuts/base/view_ops.go
  • shortcuts/common/flag_aliases.go
  • shortcuts/common/flag_context.go
  • shortcuts/common/page_all_flags.go
  • shortcuts/common/paginate_into.go
  • shortcuts/common/paginate_into_test.go
  • shortcuts/common/runner.go
  • shortcuts/common/runner_flag_alias_test.go
  • shortcuts/common/runner_normalize_test.go
  • shortcuts/common/types.go
  • shortcuts/im/builders_test.go
  • shortcuts/im/coverage_additional_test.go
  • shortcuts/im/helpers.go
  • shortcuts/im/im_chat_list.go
  • shortcuts/im/im_chat_list_test.go
  • shortcuts/im/im_chat_members_list.go
  • shortcuts/im/im_chat_messages_list.go
  • shortcuts/im/im_chat_messages_list_test.go
  • shortcuts/im/im_chat_search.go
  • shortcuts/im/im_chat_search_test.go
  • shortcuts/im/im_feed_group_item_test.go
  • shortcuts/im/im_feed_group_list.go
  • shortcuts/im/im_feed_group_list_item.go
  • shortcuts/im/im_flag_aliases_test.go
  • shortcuts/im/im_flag_list.go
  • shortcuts/im/im_list_page_all_test.go
  • shortcuts/im/im_list_pagination.go
  • shortcuts/im/im_messages_mget.go
  • shortcuts/im/im_messages_search.go
  • shortcuts/im/im_page_size_validation_test.go
  • shortcuts/im/im_search_notice_test.go
  • shortcuts/im/im_threads_messages_list.go
  • shortcuts/im/im_threads_messages_list_test.go
  • shortcuts/im/mute_filter.go
  • shortcuts/im/mute_filter_test.go
  • shortcuts/im/sort_flags.go
  • shortcuts/im/sort_flags_test.go
  • shortcuts/im/with_sender_name_test.go
  • shortcuts/mail/mail_triage.go
  • shortcuts/mail/mail_triage_test.go
  • shortcuts/sheets/flag_ergonomics.go
  • shortcuts/sheets/lark_sheet_history_list.go
  • shortcuts/sheets/shortcuts.go
  • shortcuts/sheets/shortcuts_alias_test.go
  • shortcuts/slides/presentation_flag.go
  • shortcuts/slides/shortcuts.go
  • shortcuts/slides/shortcuts_alias_test.go
  • shortcuts/slides/slides_history.go
  • shortcuts/slides/slides_media_upload.go
  • shortcuts/slides/slides_replace_pages.go
  • shortcuts/slides/slides_replace_slide.go
  • shortcuts/slides/slides_screenshot.go
  • shortcuts/slides/slides_xml_get.go
  • skills/lark-im/SKILL.md
  • skills/lark-im/references/lark-im-chat-list.md
  • skills/lark-im/references/lark-im-chat-members-list.md
  • skills/lark-im/references/lark-im-chat-messages-list.md
  • skills/lark-im/references/lark-im-chat-search.md
  • skills/lark-im/references/lark-im-feed-shortcut-list.md
  • skills/lark-im/references/lark-im-threads-messages-list.md
  • tests/cli_e2e/base/base_limit_dryrun_test.go
  • tests/cli_e2e/im/im_flag_aliases_dryrun_test.go
  • tests/cli_e2e/im/im_list_page_all_dryrun_test.go
  • tests/cli_e2e/im/im_page_all_live_test.go
  • tests/cli_e2e/mail/mail_triage_dryrun_test.go
  • tests/cli_e2e/sheets/sheets_token_alias_dryrun_test.go
💤 Files with no reviewable changes (3)
  • shortcuts/base/base_shortcut_helpers.go
  • shortcuts/base/base_dryrun_ops_test.go
  • internal/output/testdata/runtime_context_legacy.golden.json

Comment on lines +105 to +115
if nextPageToken == "" {
return meta, invalidPageCursor("response reports more pages but returned no page token")
}
if _, repeated := seen[nextPageToken]; repeated {
return meta, invalidPageCursor("response repeated page token %q, which would paginate forever", nextPageToken)
}

meta.NextToken = nextPageToken
if pageNumber == policy.maxPages {
return meta, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Repeated-cursor detection also fires for single-page reads.

The default policy is maxPages: 1. In that mode no second request is ever made, so a repeated cursor cannot cause an unbounded walk. The check at Line 108 still runs, and seen already contains the caller's --page-token. An endpoint that echoes the request cursor in page_token therefore turns a previously successful single-page read into a CategoryInternal/SubtypeInvalidResponse failure. Move the budget stop before the repeated-cursor check so the check only guards runs that will issue another request.

🐛 Proposed fix: stop at the page budget before diagnosing the cursor
 		if nextPageToken == "" {
 			return meta, invalidPageCursor("response reports more pages but returned no page token")
 		}
+		meta.NextToken = nextPageToken
+		if pageNumber == policy.maxPages {
+			return meta, nil
+		}
 		if _, repeated := seen[nextPageToken]; repeated {
 			return meta, invalidPageCursor("response repeated page token %q, which would paginate forever", nextPageToken)
 		}
-
-		meta.NextToken = nextPageToken
-		if pageNumber == policy.maxPages {
-			return meta, nil
-		}

paginate_into_test.go:313-349 pins the current behavior and must be updated with this change.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if nextPageToken == "" {
return meta, invalidPageCursor("response reports more pages but returned no page token")
}
if _, repeated := seen[nextPageToken]; repeated {
return meta, invalidPageCursor("response repeated page token %q, which would paginate forever", nextPageToken)
}
meta.NextToken = nextPageToken
if pageNumber == policy.maxPages {
return meta, nil
}
if nextPageToken == "" {
return meta, invalidPageCursor("response reports more pages but returned no page token")
}
meta.NextToken = nextPageToken
if pageNumber == policy.maxPages {
return meta, nil
}
if _, repeated := seen[nextPageToken]; repeated {
return meta, invalidPageCursor("response repeated page token %q, which would paginate forever", nextPageToken)
}
🤖 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 `@shortcuts/common/paginate_into.go` around lines 105 - 115, In the pagination
flow around the maxPages check, return successfully when pageNumber reaches
policy.maxPages before validating nextPageToken against seen. Keep requiring a
non-empty token when another request is allowed, and update the affected
paginate_into tests to expect echoed cursors to succeed for single-page reads.

Comment on lines +38 to +40
factory := newTestFactory()
factory.IOStreams.In = strings.NewReader("resolved-input")
cmd := newTestShortcutCmd(s, factory)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Locate newTestFactory and newTestShortcutCmd, and check how the factory is constructed.
set -euo pipefail

echo "=== newTestFactory definition ==="
ast-grep run --pattern 'func newTestFactory($$$) { $$$ }' --lang go shortcuts/common

echo "=== newTestShortcutCmd definition ==="
ast-grep run --pattern 'func newTestShortcutCmd($$$) { $$$ }' --lang go shortcuts/common

echo "=== mountTestShortcut definition ==="
ast-grep run --pattern 'func mountTestShortcut($$$) { $$$ }' --lang go shortcuts/common

echo "=== TestFactory / LARKSUITE_CLI_CONFIG_DIR usage in shortcuts/common ==="
rg -nP --type=go -C 3 'cmdutil\.TestFactory|LARKSUITE_CLI_CONFIG_DIR' shortcuts/common

Repository: larksuite/cli

Length of output: 185


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== files under shortcuts/common ==="
git ls-files shortcuts/common

echo "=== symbols containing testFactory/testShortcutCmd/mountTestShortcut ==="
rg -n --type=go 'newTestFactory|newTestShortcutCmd|mountTestShortcut' shortcuts/common || true

echo "=== relevant file around new helper definitions ==="
if [ -f shortcuts/common/runner_normalize_test.go ]; then
  wc -l shortcuts/common/runner_normalize_test.go
  sed -n '1,120p' shortcuts/common/runner_normalize_test.go | cat -n
fi

echo "=== factory references in shortcuts/common test files ==="
rg -n --type=go 'Factory\{|LARKSUITE_CLI_CONFIG_DIR|test.Config|Config\(\)' shortcuts/common || true

Repository: larksuite/cli

Length of output: 10538


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== cmdutil.TestFactory implementation references ==="
rg -n --type=go 'func TestFactory|func NewFactory|LARKSUITE_CLI_CONFIG_DIR' . --glob '*cmdutil*.go' | head -80 || true

echo "=== cmdutil files ==="
git ls-files | rg 'cmdutil|factory' | head -80

echo "=== runner.go config call context ==="
sed -n '880,960p' shortcuts/common/runner.go | cat -n

echo "=== runner_jq_test helper context ==="
sed -n '210,235p' shortcuts/common/runner_jq_test.go | cat -n

Repository: larksuite/cli

Length of output: 5869


Use cmdutil.TestFactory in the shortcut tests.

newTestFactory returns &cmdutil.Factory{} directly, so runShortcut reads test-created config instead of isolated CLI config state. Set LARKSUITE_CLI_CONFIG_DIR to t.TempDir() with t.Setenv and create the factory through cmdutil.TestFactory(t, config).

🤖 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 `@shortcuts/common/runner_normalize_test.go` around lines 38 - 40, Update the
shortcut test setup around newTestFactory and newTestShortcutCmd to use
cmdutil.TestFactory(t, config) instead of constructing an empty factory
directly. Set LARKSUITE_CLI_CONFIG_DIR to t.TempDir() with t.Setenv before
creating the factory, while preserving the existing input stream and command
setup.

Source: Coding guidelines

Comment on lines +79 to +81
if err := runShortcut(cmd, factory, s, true); err == nil {
t.Fatal("runShortcut() error = nil")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the error identity, not only that an error occurred.

The test accepts any non-nil error. Identity resolution, configuration load, or the scope check can each fail before Normalize runs. In that case Validate and Execute never run, their t.Fatal guards never fire, and the test passes without exercising the normalization failure path.

Assert the typed metadata and the param, as this file already does at lines 108-114.

💚 Proposed fix to pin the error identity
-	if err := runShortcut(cmd, factory, s, true); err == nil {
-		t.Fatal("runShortcut() error = nil")
-	}
+	err := runShortcut(cmd, factory, s, true)
+	var validationErr *errs.ValidationError
+	if !errors.As(err, &validationErr) {
+		t.Fatalf("error = %T %v, want typed validation error", err, err)
+	}
+	if validationErr.Param != "--legacy" {
+		t.Fatalf("param = %q, want --legacy", validationErr.Param)
+	}

As per coding guidelines: "Error-path tests must assert typed metadata through errs.ProblemOf (category, subtype, and param) and verify cause preservation rather than relying only on message substrings."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err := runShortcut(cmd, factory, s, true); err == nil {
t.Fatal("runShortcut() error = nil")
}
err := runShortcut(cmd, factory, s, true)
var validationErr *errs.ValidationError
if !errors.As(err, &validationErr) {
t.Fatalf("error = %T %v, want typed validation error", err, err)
}
if validationErr.Param != "--legacy" {
t.Fatalf("param = %q, want --legacy", validationErr.Param)
}
🤖 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 `@shortcuts/common/runner_normalize_test.go` around lines 79 - 81, Update the
error assertion around runShortcut in the normalization failure test to inspect
errs.ProblemOf rather than only checking for a non-nil error. Assert the
expected category, subtype, and param values, and verify the original cause is
preserved, following the existing typed-metadata assertions in this file’s test
around lines 108-114.

Source: Coding guidelines

}

func TestMountedShortcutNormalizeDoesNotExpandCobraPreRun(t *testing.T) {
normalizeCalled := false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The normalizeCalled assertion cannot fail.

The test calls only ParseFlags and ValidateRequiredFlags. It never calls runShortcut, which is the only caller of Normalize. So normalizeCalled is always false, independent of the implementation. The assertion at lines 147-149 passes even if the normalization ordering is reverted.

The assertions at lines 141-146 are the real contract checks and are correct. Either drop the flag, or make the test invoke the pipeline so the ordering claim is actually exercised.

💚 Proposed fix to remove the tautological assertion
 func TestMountedShortcutNormalizeDoesNotExpandCobraPreRun(t *testing.T) {
-	normalizeCalled := false
 	shortcut := Shortcut{
 		Service: "test", Command: "+normalize-required", Description: "x",
 		Flags: []Flag{
 			{Name: "canonical", Required: true},
 			{Name: "legacy", Hidden: true},
 		},
 		Normalize: func(_ context.Context, flags *FlagContext) error {
-			normalizeCalled = true
 			if !flags.Changed("legacy") || flags.Changed("canonical") {
 				return nil
 			}
 			return flags.SetCanonicalFrom("legacy", "canonical", flags.Str("legacy"))
 		},
 		Execute: func(context.Context, *RuntimeContext) error { return nil },
 	}
 	cmd := mountTestShortcut(t, shortcut)
 	if err := cmd.ParseFlags([]string{"--legacy", "accepted"}); err != nil {
 		t.Fatal(err)
 	}
 	if cmd.PreRunE != nil || cmd.PreRun != nil {
 		t.Fatal("Normalize must not install or take over Cobra PreRun hooks")
 	}
 	if err := cmd.ValidateRequiredFlags(); err == nil {
 		t.Fatal("a business Normalize hook must not satisfy Cobra Required")
 	}
-	if normalizeCalled {
-		t.Fatal("Normalize ran before Cobra Required validation")
-	}
 }

As per coding guidelines: "contract tests must assert the changed field or behavior directly so reverting the implementation causes failure."

Also applies to: 147-149

🤖 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 `@shortcuts/common/runner_normalize_test.go` at line 121, Remove the
ineffective normalizeCalled flag and its assertion from the test, since the
current flow never invokes Normalize through runShortcut. Keep the direct
contract assertions around ParseFlags and ValidateRequiredFlags unchanged, or
update the test to exercise runShortcut if ordering coverage is required.

Source: Coding guidelines

Comment on lines +58 to +67
legacy := flags.Str(legacyName)
if legacy == "" {
if flags.Changed(canonicalName) {
return nil
}
if err := flags.SetCanonicalFrom(legacyName, canonicalName, ""); err != nil {
return err
}
return nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Empty legacy value silently overrides the canonical flag's default.

When --sort-type is explicitly set to an empty string, legacy == "" is not a member of values (same as any other unrecognized value), but this branch skips validation and calls flags.SetCanonicalFrom(legacyName, canonicalName, "") instead of returning an error. This forces the canonical flag from its declared default (e.g. "create_time") to an explicit "".

Contrast this with the non-empty path at Line 79: an unrecognized value such as "unexpected" correctly returns a validation error attributed to --sort-type. An empty value is equally unrecognized but is silently accepted, and the resulting invalid state surfaces later (if at all) as an Enum failure attributed to --sort, not --sort-type, which defeats the "validation attribution" design goal.

Treat an empty explicit legacy value the same way as any other value not in values, or treat it as a no-op (leave the canonical flag at its own default). Do not force the canonical flag into an unsupported empty state.

The test at shortcuts/im/sort_flags_test.go Lines 46-47 currently encodes this behavior and needs updating alongside this fix.

🐛 Proposed fix to validate empty legacy values consistently
 	legacy := flags.Str(legacyName)
+	allowed := legacySortValues(values)
 	if legacy == "" {
-		if flags.Changed(canonicalName) {
-			return nil
-		}
-		if err := flags.SetCanonicalFrom(legacyName, canonicalName, ""); err != nil {
-			return err
-		}
-		return nil
+		return common.ValidationErrorf("invalid value %q for --%s, allowed: %s", legacy, legacyName, strings.Join(allowed, ", ")).
+			WithParam("--" + legacyName)
 	}
-	allowed := legacySortValues(values)
 	canonical := ""
🤖 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 `@shortcuts/im/sort_flags.go` around lines 58 - 67, Update the legacy-flag
handling around flags.Str and SetCanonicalFrom so an explicitly provided empty
legacy value is not propagated to the canonical flag. Validate it like other
unrecognized values and attribute the error to the legacy flag, or leave the
canonical default unchanged; then update the corresponding test case in
sort_flags_test.go to reflect the corrected behavior.

Source: Coding guidelines

Comment thread skills/lark-im/SKILL.md
Comment on lines +109 to +117
| [`+chat-messages-list`](references/lark-im-chat-messages-list.md) | List messages in a chat or P2P conversation; user/bot; accepts --chat-id or --user-id, resolves P2P chat_id, supports time range, --order asc|desc sorting, auto-pagination |
| [`+chat-search`](references/lark-im-chat-search.md) | Search visible group chats by --query keyword and/or --member-ids; user/bot; e.g. look up chat_id by group name; supports type filters, sorting, auto-pagination, and --exclude-muted (user identity only) |
| [`+chat-update`](references/lark-im-chat-update.md) | Update group chat name or description; user/bot; updates a chat's name or description |
| [`+messages-mget`](references/lark-im-messages-mget.md) | Batch get messages by IDs; user/bot; fetches up to 50 om_ message IDs, formats sender names, expands thread replies |
| [`+messages-reply`](references/lark-im-messages-reply.md) | Reply to a message (supports thread replies); user/bot; supports text/markdown/post/media replies, reply-in-thread, idempotency key |
| [`+messages-resources-download`](references/lark-im-messages-resources-download.md) | Download images/files from a message; user/bot; supports automatic chunked download for large files (8MB chunks), auto-detects file extension from Content-Type |
| [`+messages-search`](references/lark-im-messages-search.md) | Search messages across chats (supports keyword, sender, time range filters) with user identity; user-only; filters by chat/sender/attachment/time, supports auto-pagination via `--page-all` / `--page-limit`, enriches results via batched mget and chats batch_query |
| [`+messages-send`](references/lark-im-messages-send.md) | Send a message to a chat or direct message; user/bot; sends to chat-id or user-id with text/markdown/post/media, supports idempotency key |
| [`+threads-messages-list`](references/lark-im-threads-messages-list.md) | List messages in a thread; user/bot; accepts om_/omt_ input, resolves message IDs to thread_id, supports sort/pagination |
| [`+threads-messages-list`](references/lark-im-threads-messages-list.md) | List messages in a thread; user/bot; accepts om_/omt_ input, resolves message IDs to thread_id, supports --order asc|desc sorting, auto-pagination |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Escape the literal pipe in "asc|desc" to fix the broken table rows.

Lines 109 and 117 contain unescaped asc|desc inside a table cell. Markdown table parsers split cells on |, so these rows render with an extra, misplaced column instead of the intended 2-column layout.

📝 Proposed fix to escape the pipe character
-| [`+chat-messages-list`](references/lark-im-chat-messages-list.md) | List messages in a chat or P2P conversation; user/bot; accepts --chat-id or --user-id, resolves P2P chat_id, supports time range, --order asc|desc sorting, auto-pagination |
+| [`+chat-messages-list`](references/lark-im-chat-messages-list.md) | List messages in a chat or P2P conversation; user/bot; accepts --chat-id or --user-id, resolves P2P chat_id, supports time range, --order asc\|desc sorting, auto-pagination |
-| [`+threads-messages-list`](references/lark-im-threads-messages-list.md) | List messages in a thread; user/bot; accepts om_/omt_ input, resolves message IDs to thread_id, supports --order asc|desc sorting, auto-pagination |
+| [`+threads-messages-list`](references/lark-im-threads-messages-list.md) | List messages in a thread; user/bot; accepts om_/omt_ input, resolves message IDs to thread_id, supports --order asc\|desc sorting, auto-pagination |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| [`+chat-messages-list`](references/lark-im-chat-messages-list.md) | List messages in a chat or P2P conversation; user/bot; accepts --chat-id or --user-id, resolves P2P chat_id, supports time range, --order asc|desc sorting, auto-pagination |
| [`+chat-search`](references/lark-im-chat-search.md) | Search visible group chats by --query keyword and/or --member-ids; user/bot; e.g. look up chat_id by group name; supports type filters, sorting, auto-pagination, and --exclude-muted (user identity only) |
| [`+chat-update`](references/lark-im-chat-update.md) | Update group chat name or description; user/bot; updates a chat's name or description |
| [`+messages-mget`](references/lark-im-messages-mget.md) | Batch get messages by IDs; user/bot; fetches up to 50 om_ message IDs, formats sender names, expands thread replies |
| [`+messages-reply`](references/lark-im-messages-reply.md) | Reply to a message (supports thread replies); user/bot; supports text/markdown/post/media replies, reply-in-thread, idempotency key |
| [`+messages-resources-download`](references/lark-im-messages-resources-download.md) | Download images/files from a message; user/bot; supports automatic chunked download for large files (8MB chunks), auto-detects file extension from Content-Type |
| [`+messages-search`](references/lark-im-messages-search.md) | Search messages across chats (supports keyword, sender, time range filters) with user identity; user-only; filters by chat/sender/attachment/time, supports auto-pagination via `--page-all` / `--page-limit`, enriches results via batched mget and chats batch_query |
| [`+messages-send`](references/lark-im-messages-send.md) | Send a message to a chat or direct message; user/bot; sends to chat-id or user-id with text/markdown/post/media, supports idempotency key |
| [`+threads-messages-list`](references/lark-im-threads-messages-list.md) | List messages in a thread; user/bot; accepts om_/omt_ input, resolves message IDs to thread_id, supports sort/pagination |
| [`+threads-messages-list`](references/lark-im-threads-messages-list.md) | List messages in a thread; user/bot; accepts om_/omt_ input, resolves message IDs to thread_id, supports --order asc|desc sorting, auto-pagination |
| [`+chat-messages-list`](references/lark-im-chat-messages-list.md) | List messages in a chat or P2P conversation; user/bot; accepts --chat-id or --user-id, resolves P2P chat_id, supports time range, --order asc\|desc sorting, auto-pagination |
| [`+chat-search`](references/lark-im-chat-search.md) | Search visible group chats by --query keyword and/or --member-ids; user/bot; e.g. look up chat_id by group name; supports type filters, sorting, auto-pagination, and --exclude-muted (user identity only) |
| [`+chat-update`](references/lark-im-chat-update.md) | Update group chat name or description; user/bot; updates a chat's name or description |
| [`+messages-mget`](references/lark-im-messages-mget.md) | Batch get messages by IDs; user/bot; fetches up to 50 om_ message IDs, formats sender names, expands thread replies |
| [`+messages-reply`](references/lark-im-messages-reply.md) | Reply to a message (supports thread replies); user/bot; supports text/markdown/post/media replies, reply-in-thread, idempotency key |
| [`+messages-resources-download`](references/lark-im-messages-resources-download.md) | Download images/files from a message; user/bot; supports automatic chunked download for large files (8MB chunks), auto-detects file extension from Content-Type |
| [`+messages-search`](references/lark-im-messages-search.md) | Search messages across chats (supports keyword, sender, time range filters) with user identity; user-only; filters by chat/sender/attachment/time, supports auto-pagination via `--page-all` / `--page-limit`, enriches results via batched mget and chats batch_query |
| [`+messages-send`](references/lark-im-messages-send.md) | Send a message to a chat or direct message; user/bot; sends to chat-id or user-id with text/markdown/post/media, supports idempotency key |
| [`+threads-messages-list`](references/lark-im-threads-messages-list.md) | List messages in a thread; user/bot; accepts om_/omt_ input, resolves message IDs to thread_id, supports --order asc\|desc sorting, auto-pagination |
🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 109-109: Table column count
Expected: 2; Actual: 3; Too many cells, extra data will be missing

(MD056, table-column-count)


[warning] 117-117: Table column count
Expected: 2; Actual: 3; Too many cells, extra data will be missing

(MD056, table-column-count)

🤖 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 `@skills/lark-im/SKILL.md` around lines 109 - 117, Escape the literal pipe in
the “asc|desc” text within the +chat-messages-list and +threads-messages-list
table entries so Markdown treats it as cell content and preserves the intended
table structure.

Source: Linters/SAST tools

Comment on lines +90 to +101
func TestBaseListDryRunValidatesPageSizeAliasAsCanonicalLimit(t *testing.T) {
setBaseDryRunConfigEnv(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
t.Cleanup(cancel)

result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{
"base", "+table-list",
"--base-token", "app_x",
"--limit", "20",
"--page-size", "40",
"--dry-run",
},
Args: []string{"base", "+table-list", "--base-token", "app_x", "--page-size", "101", "--dry-run"},
DefaultAs: "bot",
})
require.NoError(t, err)
result.AssertExitCode(t, 2)

require.Equal(t, "validation", gjson.Get(result.Stderr, "error.type").String(), result.Stderr)
require.Equal(t, "invalid_argument", gjson.Get(result.Stderr, "error.subtype").String(), result.Stderr)
require.Equal(t, "--page-size", gjson.Get(result.Stderr, "error.param").String(), result.Stderr)
require.Contains(t, gjson.Get(result.Stderr, "error.message").String(), "mutually exclusive")
require.Empty(t, result.Stdout)
require.Equal(t, "--limit", gjson.Get(result.Stderr, "error.param").String(), result.Stderr)
require.Contains(t, gjson.Get(result.Stderr, "error.message").String(), "must be between 1 and 100")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert the complete validation error contract.

This test only checks error.param and the message. It will pass if the error category or subtype changes. It will also pass if validation output pollutes stdout. Assert error.type == "validation", error.subtype == "invalid_argument", and empty stdout.

As per coding guidelines, error-path tests must assert typed metadata. Based on learnings, validation E2E failures must write the typed JSON envelope to stderr and leave stdout empty.

🤖 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 `@tests/cli_e2e/base/base_limit_dryrun_test.go` around lines 90 - 101,
Strengthen TestBaseListDryRunValidatesPageSizeAliasAsCanonicalLimit by asserting
the complete validation error contract: verify error.type is "validation",
error.subtype is "invalid_argument", and stdout is empty, while preserving the
existing error.param and message assertions.

Sources: Coding guidelines, Learnings

Comment on lines +45 to +70
func TestMail_TriageDryRunUsesPageSizeAsExactMaxAlias(t *testing.T) {
setMailTriageDryRunEnv(t)

tests := []struct {
name string
args []string
want int64
}{
{name: "alias only", args: []string{"--page-size", "5"}, want: 5},
{name: "alias last", args: []string{"--max", "7", "--page-size", "5"}, want: 5},
{name: "canonical last", args: []string{"--page-size", "5", "--max", "7"}, want: 7},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
t.Cleanup(cancel)
args := []string{"mail", "+triage", "--mailbox", "me"}
args = append(args, test.args...)
args = append(args, "--dry-run")
result, err := clie2e.RunCmd(ctx, clie2e.Request{Args: args, DefaultAs: "user"})
require.NoError(t, err)
result.AssertExitCode(t, 0)
require.Equal(t, test.want, clie2e.DryRunGet(result.Stdout, "api.0.params.page_size").Int(), result.Stdout)
})
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 --glob '*_test.go' '\+triage|Mail.*Triage|mail.*triage' tests/cli_e2e/mail

Repository: larksuite/cli

Length of output: 6191


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== mail draft send workflow triage section =="
sed -n '120,175p' tests/cli_e2e/mail/mail_draft_send_workflow_test.go

echo
echo "== mail e2e bootstrap/setup references =="
rg -n --glob '*_test.go' 'mailboxID|Larksuite_CLI_TEST_MAIL_|create|bot|credentials|New.*Mail|TestMain|Setup|teardown|defer\(|cleanup|Tearing|Cleanup' tests/cli_e2e/mail

echo
echo "== triage dryrun test full =="
sed -n '1,90p' tests/cli_e2e/mail/mail_triage_dryrun_test.go

echo
echo "== other +triage usages in e2e tests =="
rg -n '\+triage|DryRunGet|page_size|max_alias|--dry-run' tests/cli_e2e --glob '*_test.go'

Repository: larksuite/cli

Length of output: 50370


Add live E2E coverage for the mail triage alias behavior.

These dry-run tests only inspect emitted request params. Add a self-contained live mail +triage workflow that exercises the alias through command execution and asserts the changed behavior.

🤖 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 `@tests/cli_e2e/mail/mail_triage_dryrun_test.go` around lines 45 - 70, Add a
self-contained live E2E test alongside
TestMail_TriageDryRunUsesPageSizeAsExactMaxAlias that executes mail +triage with
the relevant --page-size/--max alias combinations against the configured test
mailbox, rather than inspecting dry-run request parameters. Assert the command
succeeds and verify the resulting triage behavior reflects the alias precedence
and exact page-size/max semantics.

Source: Coding guidelines

Comment on lines +53 to +55
{name: "alias only", args: []string{"--page-size", "5"}, want: 5},
{name: "alias last", args: []string{"--max", "7", "--page-size", "5"}, want: 5},
{name: "canonical last", args: []string{"--page-size", "5", "--max", "7"}, want: 7},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve canonical --max precedence.

--page-size is documented here as an exact alias of --max. The alias last case expects --page-size to override canonical --max. This conflicts with the PR objective that requires canonical precedence.

Expect 7 when both flags are present, regardless of argument order.

Proposed test update
-		{name: "alias last", args: []string{"--max", "7", "--page-size", "5"}, want: 5},
+		{name: "canonical wins when alias is last", args: []string{"--max", "7", "--page-size", "5"}, want: 7},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{name: "alias only", args: []string{"--page-size", "5"}, want: 5},
{name: "alias last", args: []string{"--max", "7", "--page-size", "5"}, want: 5},
{name: "canonical last", args: []string{"--page-size", "5", "--max", "7"}, want: 7},
{name: "alias only", args: []string{"--page-size", "5"}, want: 5},
{name: "canonical wins when alias is last", args: []string{"--max", "7", "--page-size", "5"}, want: 7},
{name: "canonical last", args: []string{"--page-size", "5", "--max", "7"}, want: 7},
🤖 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 `@tests/cli_e2e/mail/mail_triage_dryrun_test.go` around lines 53 - 55, Update
the test cases around the mail triage dry-run flag parsing to preserve canonical
--max precedence: when both --max and its --page-size alias are supplied, expect
the --max value regardless of argument order. Change the “alias last” case to
expect 7 while keeping the single-flag and “canonical last” cases aligned with
this behavior.

@fangshuyu-768 fangshuyu-768 removed the domain/ccm PR touches the ccm domain label Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/base PR touches the base domain domain/im PR touches the im domain domain/mail PR touches the mail domain size/XL Architecture-level or global-impact change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants