Skip to content

feat(sheets): error prescription overhaul, batch-update hardening, and read offload - #2091

Open
xiongyuanwen-byted wants to merge 15 commits into
mainfrom
feat/lark-sheets-develop
Open

feat(sheets): error prescription overhaul, batch-update hardening, and read offload#2091
xiongyuanwen-byted wants to merge 15 commits into
mainfrom
feat/lark-sheets-develop

Conversation

@xiongyuanwen-byted

@xiongyuanwen-byted xiongyuanwen-byted commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Aggregates the sheets work on feat/lark-sheets-develop since #1833, driven by eval-batch error attribution (07-11 / 07-21 / 07-28 batches). Four themes:

Error messages as prescriptions

  • Inline schema hints on validation failure — the error itself teaches the expected shape.
  • Collect all validation errors in one pass instead of failing on the first.
  • Errors and help now name the enum, the required selector, and the missing envelope explicitly.
  • Reachable border-vocabulary normalization (previously dead code), plus prescriptive fixes for flag and style-field mistakes.

Batch-update contract hardening (#2028)

  • Contract hardening and style-vocabulary acceptance for +batch-update, from a dedicated 137-error attribution round and five re-run validations, including a cross-model batch (Doubao).

Read-path improvements

  • --include truncation support on +cells-get.
  • --output-path for full-read file offload on cells / csv / table-get, avoiding stdout truncation on large reads.

Correctness fixes

  • +dim-insert --inherit-style side mapping corrected (backend always inherits the anchor column's style; side only picks placement): beforeside=after + pos-1, afterside=before; redundant none dropped.
  • Reject path-shaped --csv values instead of silently writing them into the sheet.
  • Skill docs and flag data resynced from sheet-skill-spec.

origin/main has been merged back into this branch (conflicts resolved in internal/validate/path.go and internal/vfs/localfileio/path.go, keeping both SafeTempAbsInputPath and LocalInputPath).

Test plan

Summary by CodeRabbit

  • New Features

    • Added declarative +styles-put for styles, merges, row/column sizing, and freezes.
    • Added multi-region +cells-set --writes and multi-range +dim-delete --ranges.
    • Added +chart-create --print-example and improved --print-schema --flag-name for dotted paths.
  • Bug Fixes

    • Improved confirmation retry hints (copy-paste re-run … --yes when safe).
    • Strengthened CSV/file-path safety checks and safer temp input handling.
    • Improved batch-update validation aggregation and clarified partial-failure behavior.
  • Documentation

    • Updated Sheets flag docs and references to reflect new flags/semantics (including --output-path, truncation reporting, and whole-sheet defaults like optional --range where supported).

anunwu-byted and others added 13 commits July 15, 2026 18:13
为什么:豆包 Excel Agent 案例中模型 7 次参数错误,报错只说"错了"不说
"怎么改对",模型反复试错并静默降级交付(5 张饼图丢数据标签)。

怎么改:
- strict unexpected-property 报错附该节点合法 key 列表(cap 15 截断)
  + did-you-mean(复用 internal/suggest)
- required-missing 报错附缺失字段的 type/description/enum 一行提示
- 深层 type mismatch 报错附该字段 enum/description 后缀
- +batch-update 顶层 --sheet-id/--sheet-name(含下划线拼写)特判,
  直接指明 per-op locator 契约,不给误导性 fuzzy 建议
- batch sub-op input 拒收 cell_styles/styles/cell_merges 包裹结构,
  报错教学扁平 flags 写法
- 守护测试锁定 wrappedSubOpInputKeys 与 batchOpDispatch 的互斥假设

约束:不改 legacy 报错措辞前缀、保留 --print-schema 指针、不动宽松
AdditionalProperties 设计(内嵌 schema 当前无 strict 节点,该路径为预置)。

验证:gofmt -l 无输出、go vet 干净、go test -count=1 ./shortcuts/sheets/
./internal/suggest/ 全部通过。
## Background

Round 2 of eval-driven sheets optimization, rebased onto the latest `feat/lark-sheets-develop` (`8897196d`).

## Changes

- **feat(sheets): cut agent error rate and --help lookups (eval round 2)** — targets the top failure modes from round 2 evals, reducing agent error rate and the number of `--help` lookups.
- **chore(sheets): sync skill docs and flag data from sheet-skill-spec** — syncs skill docs and flag data from sheet-skill-spec.

## Notes

- During rebase, the "import mislabeled .xls workbooks by sniffing content" fix already existed on the target branch (identical patch-id), so it was auto-skipped — no duplicate.
- The target branch was force-rewritten and advanced in the meantime; the two new commits were cleanly replayed onto the new tip via `--onto` with no conflicts. One hunk touching the `--type` description in `lark-sheets-workbook.md` was auto-dropped because upstream already has the same end state — no content lost.
Map the new `truncation` value in --include to include_truncation_info on
the get_cell_ranges tool input, so +cells-get can return per-cell
isRowTruncated / isColTruncated. Flag metadata and reference synced from
sheet-skill-spec; flag_defs_gen.go regenerated.
…sv/table-get

Reads are capped by max_chars (default 500000; the backend tool also truncates
at ~50000 when unset). Add --output-path to +cells-get / +csv-get / +table-get:
when set, the result is written to a cwd-relative path as JSON and the char cap
is lifted to unbounded, so a large sheet lands on disk in full instead of being
clipped for stdout.

+table-get previously never sent max_chars, so it silently dropped rows past the
backend ~50000 default with no signal. It now takes --max-chars (default 500000,
sent explicitly) and surfaces truncated / truncation_warning when the read is
clipped, steering callers to --output-path for a lossless full read.
为什么:fail-fast 单错报错导致「挤牙膏」修复回路——修一处、重试、
撞下一处。评测实测(turbo 三批 247 次失败)约 32% 的失败轮次是
挤牙膏,其中 local→local 类(39 次)本可一次报出。

怎么改:
- validateAgainstSchema 重构为 collectSchemaErrors 收集器版:命中
  错误后继续遍历,全部问题一次报出(每条带各自的教学信息)
- translateBatchOperations 同步做 op 级聚合:多个坏 op 一条报错
  编号列出,不再 fail-fast 在第一个
- 三条护栏:单错误输出逐字不变(向后兼容);显示 cap 5 条 + 收集
  到 6 即全树短路(病态大数组不爆炸);类型错节点不下钻、oneOf
  用一次性探测器(防级联噪音/误报泄漏)

验证:gofmt/vet 干净,go test -count=1 全过(既有测试零改动,
新增 5 个聚合测试:多错编号、cap 截断、oneOf 不泄漏、batch 双 op
聚合、单 op 保持原文)。
…ceptance (#2028)

* feat(sheets): harden +batch-update sub-op contract (P0-1/2/3/5)

Eval-driven fixes for the top +batch-update error clusters (137 errors
across 7 eval batches, attribution in the optimization plan doc):

- Reject unknown sub-op input keys with did-you-mean + full key contract
  instead of silently ignoring them (silent ignore surfaced as misleading
  'missing required flag' errors — the largest cluster, ~35 hits).
  Habitual spellings are rewritten in place: camelCase -> snake_case,
  commandFlagAliases (new: size -> width/height on the resize pair, the
  pre-July vocabulary and the --styles protocol spelling, 15+ hits),
  single-entry ranges unwraps onto range.
- Aggregate per-op validation errors into one pass (each op's first
  error) instead of fail-fast first-error-only; single-error batches
  keep the standalone-shaped error (contract tests unchanged).
- Precheck cells matrix vs range locally: empty cells (prescribes
  +cells-clear) and row/column count mismatches no longer reach the
  server mid-batch.
- Correct the atomicity story: execution is fail-fast without rollback
  (verified against live batches; docs promised a rollback that does
  not happen). Partial-failure errors now spell out that succeeded
  operations stay applied and prescribe resending only the failed
  tail, preventing double-apply on retry.

* feat(cli): accept @file payload reads from the system temp dir (P0-6)

Agents stage generated payloads (batch operations JSON, CSV) in /tmp as
a matter of course; the relative-to-cwd-only policy pushed every
--operations @/tmp/ops.json through an extra python/stdin round trip
(recurring friction cluster in eval traces).

Scope is deliberately narrow: a new SafeTempAbsInputPath accepts an
absolute READ path only when it resolves (symlinks included) under the
canonical os.TempDir(), and only the @file expansion in
cmdutil.ReadInputFile uses it. SafeInputPath stays strict — uploads,
drive sync and the CI quality gates treat 'absolute paths rejected' as
a load-bearing invariant. Absolute paths outside the temp dir now get
a prescriptive error naming all three options (relative path, temp-dir
path, stdin).

* feat(sheets): add +styles-put, +dim-delete --ranges, freeze in --styles

Batch-B of the +batch-update overhaul (attribution: ~73% of real batch
calls were pure formatting finishers hand-built as operations arrays).

- +styles-put: declarative visual spec for existing spreadsheets.
  Reuses the workbook-create/table-put --styles parser (identical
  vocabulary and aggregate-all-issues errors), expands client-side into
  ONE atomic batch_update per spec: cell_merges -> cell_styles ->
  row_sizes -> col_sizes -> freeze. Style stamps are safe to re-run.
  Verified live: 6/6 sub-ops applied, frozen rows / merges / row
  heights confirmed by read-back.
- freeze section added to the shared --styles pipeline ({rows, cols}),
  so +workbook-create and +table-put gain it too — closes the one gap
  that still forced a separate +dim-freeze call.
- +dim-delete --ranges: scattered row/column ranges in one atomic
  batch, ordered DESCENDING so earlier deletions never shift later
  indexes (the recurring failure of hand-built dim-delete batches);
  same-dimension and non-overlap enforced, nesting inside
  +batch-update rejected with a prescription.
- +cells-batch-set-style enters phase-1 deprecation: kept working,
  docs point at +styles-put, an in-band note steers new usage.

Skill docs regenerated from sheet-skill-spec (new
lark-sheets-styles-put reference, three-way dispatch in guideline 6,
fail-fast-no-rollback wording).

* feat(sheets): accept height/width as one-way aliases for size in --styles row/col_sizes

size stays the canonical dimension key: it keeps row_sizes and col_sizes
items shape-uniform (the array name already carries the dimension), it is
what shipped with workbook-create/table-put and what models demonstrably
converge on, and it matches the dimension-neutral precedent of comparable
APIs. The Excel-vocabulary words are accepted silently only where
unambiguous — height inside row_sizes, width inside col_sizes; the wrong
dimension's word gets a targeted error instead of a rewrite, and giving
both size and the alias is rejected. Shared parser, so +workbook-create /
+table-put / +styles-put all gain it.

* chore(sheets): sync skill docs — +cells-batch-set-style fully exits the skill surface

Regenerated from sheet-skill-spec: the deprecated command no longer
appears anywhere in SKILL.md or the references (multi-range styling
routes to +styles-put); its flag-defs entry is untouched, so the command
and --help keep working for compatibility callers, with the in-band
supersedence note steering them to +styles-put.

* fix(sheets): forgive habitual vocabulary on the --styles payload path

07-20 rerun attribution: the batch-update dispatch worked (calls 105->25,
errors 21->8; +styles-put adopted by 16/35 tasks) but +styles-put itself
hit a 56% stateful error rate — the redesign moved traffic from the flag
path onto the payload path, and the round-2 forgiveness layers (key
aliases, enum-value canonicalization) only existed on the flag path.
Both dominant clusters are fixed in the shared styles pipeline, so
+styles-put / +table-put / +workbook-create / typed --cells all gain it:

- border family folding (largest cluster, up to 88 issues in one retry):
  borders/border objects, border_top..right objects, border_style/color/
  weight scalars, and flattened border_<side>_<attr> keys all fold into
  the canonical nested border_styles; border_style:"thin" reads as a
  thin solid line (weight vocabulary in the style slot).
- enum VALUE canonicalization inside cell_styles (~10 server-side
  round trips: vertical_alignment "center" -> "middle"), sourced from
  the +cells-set-style flag enums; off-enum values now fail client-side
  with a did-you-mean instead of failing the whole batch server-side.
- wrap family: wrap_text/text_wrap -> word_wrap, boolean -> enum.
- bare-string cell_merges entries read as {range, merge_type:all}.
- fore_color gets a prescription (openpyxl fgColor is the FILL color;
  a silent pick could color the wrong thing).

Replayed the eval-failing payload shapes live: 2/2 applied.

* feat(sheets): resize type optional in --styles; acceptance-surface contract tests

- {range, size} in row/col_sizes now means a pixel resize (type stays
  for standard/auto) — the payload path matches the flag path, where
  --width never required --type.
- Two closure tests turn the --styles acceptance surface into a locked
  contract instead of open-ended patching:
  * vocabulary parity — every +cells-set-style flag (iterated from
    flag-defs) must be accepted verbatim by the payload path, so a
    future flag can never again ship without payload-path support;
  * prior corpus — every model spelling observed across the 07-08..07-20
    eval batches must either normalize to canonical or produce a
    targeted prescription; silent ignoring and bare rejection both fail
    the suite. New eval finding -> add a corpus row -> fix -> locked.

Skill docs regenerated: the border shorthand ({style,weight,color} on
all four sides) is now the teaching form, border_styles demoted to
per-side differences; resize examples drop the type ceremony.

* fix(sheets): close the 07-21 rerun residuals — full-form thin, range coalescing, typed-cells style key

Valid rerun (skill injection verified at ~31k chars/task): +styles-put
stateful error rate fell 56.5% -> 34.3% and batch-update stayed at its
post-dispatch low. Three residual clusters, all closed:

- weight vocabulary in the FULL nested form's style slot
  (border_styles.<side>.style:"thin" — 8 tasks, the dominant residual;
  the earlier rewrite only covered the shorthand scalar path). Now
  normalized in expandBorderAllShorthand, the single border touchpoint
  shared by the flag, typed-cells and styles-payload paths.
- per-row specs blowing the 100-op cap (184/203/861-op expansions):
  coalesceStyleStamps fuses identical-style entries into rectangles
  (vertical fixpoint merge on same column span, horizontal on same row
  span) before the cap — a declarative spec describes intent, execution
  shape is the CLI's to optimize. Cap message now also routes
  alternating-row banding to +cond-format-create.
- typed --cells habitual keys (recurring server-side 900015206 in both
  reruns): cells[][].style object rewrites to cell_styles;
  cells[][].type gets a prescription instead of a server round trip.
  The content-in-styles message is also neutral now (was
  workbook-create-specific).

Corpus + coalescing + typed-cells tests added; live replay: full-form
thin accepted and 3 same-style rows fused, all applied.

* refactor(sheets): give the style-vocabulary acceptance layer its own home

Pure mechanical move, zero behavior change (locked by the acceptance
contract tests). The acceptance layer had grown as an accretion across
helpers.go and lark_sheet_workbook.go — deliberate design (one canonical
form + wide acceptance, per the divergent-priors evidence), accidental
placement.

style_vocab.go now holds the whole subsystem — flag-path style builders,
key aliases, enum-value canonicalization, border folding/normalization,
typed-cells cell-object rewrites — under a header that states the design
contract: rewrites must be unambiguous, ambiguity prescribes, silence
and bare rejection are both bugs, closure is enforced by the parity +
prior-corpus tests. helpers.go shrinks back to generic plumbing
(818 -> 542 lines).

The other two acceptance surfaces keep their own homes: cobra flag
ergonomics in flag_ergonomics.go, batch sub-op key vocabulary in
batch_op_dispatch.go.

* fix(sheets): enforce the cells-vs-range match on single-cell ranges too

The precheck deliberately skipped bare single-cell ranges when server
behavior was unverified; the 07-21 rerun supplied the evidence (12 rows
against range "A1" failing server-side with row count 1) — +cells-set
has no anchor semantics, the strict match applies everywhere. Corpus
updated accordingly.

* feat(sheets): make +csv-get --range optional — omitted reads the whole sheet

The tool requires a range but clips past-grid references and reports the
clip in actual_range, so the CLI sends an over-wide whole-columns range
(A:ZZZ) when --range is omitted: one call reads the entire sheet, no
workbook-info pre-flight to size it first. Eval evidence: 'required
flag(s) range not set' was the most-missed required flag on +csv-get
(4 tasks in the 07-21 batch) — the models' intent was always 'read it
all'. Blank --range now means the same as omitting it.

Skill docs updated (quick-reference row, read-data full-read example,
flag desc); round-3 backlog item A5.

* feat(sheets): add editing rule #10 — never fabricate missing values

补齐 / 扩展 / 按原表格式续填时,查不到或无法确定的值一律留空 +
备注注明,禁止用推算 / 估算 / 凭空数据充数;原表已示范缺失值写法时照抄。

Synced from sheet-skill-spec (SoT).

* fix(sheets): accept side-first border word order and wrap_strategy

07-21 evening batch (first run carrying the previous fixes — thin
full-form / style/type keys / csv-get range all at zero): the corpus
loop caught the next spelling permutations. bottom_border /
bottom_border_style (side-first word order, alongside the border_bottom
family already folded) and wrap_strategy (the Google Sheets API word)
now normalize; three corpus rows lock them.

* feat(sheets): universal did-you-mean on unknown style fields; formalize the silent-alias admission bar

The alias table was drifting toward per-permutation entries with fast-
falling marginal value (first aliases covered 15+ errors each, the last
ones 1-2). The asymmetry that matters: rejection is universal, aliases
are per-word. So:

- unknown style fields now reject with did-you-mean + the full canonical
  field list (this error had neither — the actual reason word-order
  permutations turned into multi-issue retry loops). Any future
  permutation costs one self-healing retry, zero new code, and corrects
  the whole session (silent aliases never correct the model in-session).
- the acceptance-layer contract now states the admission bar for silent
  aliases: real external vocabularies only (Excel/openpyxl, CSS, Google
  Sheets API — a finite set), recurring across batches or ≥3 tasks,
  zero ambiguity. Permutations go to the universal rejection. Existing
  permutation aliases are grandfathered.

* feat(sheets): +cells-set --writes — scattered multi-region writes in one atomic call

The last compressible batch-update scenario: eval traces show 'fix all
broken formulas across ranges/sheets' (~6 calls / 3 tasks per batch)
still hand-assembled as +batch-update operations arrays. --writes takes
[{sheet_name|sheet_id, range, cells}, ...] (up to 100 items, cross-sheet)
and fans it into ONE atomic batch_update of set_cell_range ops.

Design decisions:
- the sheet selector LIVES IN EACH ITEM — no top-level fallback, no
  precedence table; same convention models already learned from
  +batch-update sub-ops and +styles-put items. A top-level
  --sheet-name/--sheet-id with --writes is rejected with the fix.
- every item runs the exact standalone pipeline via a per-item flag
  view: key vocabulary (camelCase, aliases, did-you-mean), the style
  acceptance layer for inline cell_styles, matrix precheck, schema
  validation; item errors aggregate so one retry fixes all.
- XOR with --range/--cells/--copy-to-range; top-level --allow-overwrite
  propagates to items that don't override it.
- nesting inside +batch-update rejected (expands into its own batch).
- predictable prior handled: --styles on +cells-set now hints the
  layering (range-level styling -> +styles-put; per-cell styles ride in
  the cells objects) instead of a bare unknown-flag error.

Live smoke: value + formula regions in one call, 2/2 applied.
Expected effect: batch-update calls drop another ~30% to its
heterogeneous-atomic-chain steady state.

* chore(sheets): bump lark-sheets skill version to 3.1.0

Version roll-up for the batch-update optimization series: cells-set
--writes, universal did-you-mean on style fields, border word-order and
wrap_strategy acceptance, editing rule #10, and optional +csv-get
--range.
…edundant none

The flag name was inverted relative to actual behavior, and `none` was redundant.

- Map --inherit-style onto the modify_sheet_structure backend so the name
  matches behavior (verified on a live sheet): `before` inherits the preceding
  row/column (side=after, position-1); `after` inherits the following
  (side=before). Insertion always lands before --position.
- Warn only when `before` is used at the first row/column (no preceding
  dimension to copy from); `after` has no such edge.
- Drop the `none` enum value: it was identical to omitting the flag and
  misleadingly implied "no inheritance" (the backend always copies a
  neighbour). Omitting the flag inherits the following row/column; `none` is
  now rejected by enum validation. Clear formats afterwards for a blank one.
- Regenerate flag-defs, update tests and the skill reference.
…e flag and style-field fixes

Three fixes from the 07-28 root-cause analysis of failed agent traces, all
aimed at the first-try success rate rather than the recovery loop.

Border acceptance layer was unreachable on two of its three carrier paths.
expandBorderAllShorthand already moves a weight word out of the style slot
({"style":"thin"} -> style solid + weight thin), but on --border-styles and
typed --cells it ran after parseJSONFlag's schema check, so the enum error
fired first and the rewrite never happened. Move it ahead of validation via
the jsonFlagNormalizers seam; --styles already validated post-expansion and
is unchanged. An explicitly conflicting weight still takes the enum error.

Unknown-flag prescriptions for the names agents reach for most: +cells-set
--values, +dim-freeze --frozen-row-count and siblings, +cells-set-style
--font-bold / --bg-color / --wrap-strategy and the whole --border-* family
(no such flags; borders take one composite --border-styles). +sheet-rename
--new-name / --name alias to --title, matching +sheet-create.

Unsupported cell_styles field names now name the right field instead of the
nearest string: bold / font_bold -> font_weight, text_align ->
horizontal_alignment, a nested openpyxl-style font object -> the flat font_*
fields. Where no prescription applies, the edit-distance fallback is capped
at two edits, so a concept-swap neighbour (font_bold -> font_color, three
edits) stays silent rather than sending the retry the wrong way; a curated
prescription also drops the contradicting machine-readable suggestions.
…velope in errors and help

Second batch from the 07-28 root-cause analysis, all aimed at the retry that
follows a rejection.

Enum-bearing type mismatches now answer with the allowed values instead of a
whole-payload skeleton. --border-styles with weight:1 used to reply "expected
type string, got number; expected shape: {"bottom": {…}, "left": {…}, …}",
which never mentions thin/medium/thick; the skeleton is for container-shape
confusion, so a field that declares an enum falls through to the hint that
names it. The --border-styles help now inlines both vocabularies (style is the
line type, weight the thickness and a string, not a pixel number), spells the
{all:{…}} shorthand, and states that no --border-all / --border-top /
--border-color exist. --word-wrap additionally accepts the Google Sheets
wrapStrategy words wrap and clip.

The sheet selector states that one of the pair is required rather than only
that they are mutually exclusive, in help across all shortcuts that take it,
and the rejection hints where the name comes from: a fresh workbook has one
sheet named Sheet1, any other needs a +workbook-info lookup. Eval traces
recover on the very next call, so the gap was which name to pass.

A --sheets payload written as a bare array now says the top level must be
{"sheets":[…]} instead of quoting Go's "cannot unmarshal array into Go value
of type struct { Sheets []sheets.tableSheetIn }", which names the internal
type rather than the fix. The skeleton hint is unchanged.
…spec

Mirrors `npm run sync:cli` output from the spec repo, which is the source of
truth for skill docs and flag data. Two independent changes ride along:

- The border and sheet-selector flag descriptions from spec commit 3dd7f9c,
  matching the help text already committed here in 0b59556 (data/flag-defs.json
  is byte-identical, so `go generate` is a no-op).
- The upstream read-flow work: SKILL.md 3.1.0 to 3.1.1, a longer read-data
  reference, and five read-side helper scripts under skills/lark-sheets/scripts.

The scripts land as machine resources and are not embedded in the binary
(content_embed.go whitelists docs only). make unit-test passes.
…into the sheet

A +csv-put --csv value naming a file that doesn't resolve used to be written
into the anchor cell as literal text, with a success exit code — a wrong value
in the sheet that nothing surfaces, which costs more than a rejection. The
common source is an absolute path: @ only reads relative paths, so the caller
drops the @ and retries, and the path string lands in A1 (07-28 root-cause
audit, finding D1). The existing guard only caught values naming a file that
does exist (the forgotten-@ case).

The guard now also rejects a value that is unmistakably path-shaped: no
separator or whitespace, pure ASCII, and either a .csv/.tsv extension or an
explicit ./ ../ / ~/ prefix. All three conditions are required — that is what
keeps prose that merely mentions a filename, N/A, README.md and CJK content
out of it, the misjudgments that retired the previous name-shape heuristic.
This flips one pinned case: a bare "nope.csv" was previously written verbatim
and now errs with the fix inlined.

To make the shape check safe for correct invocations, resolveInputFlags now
records which flags had their value replaced from @file or stdin, exposed as
RuntimeContext.InputResolvedFromSource; the guard skips resolved values
entirely. By Validate time a piped value is indistinguishable from a typed
one, so without the origin bit the guard would re-reject a correct
`--csv @file` whose content happens to look like a path — and stdin, which
the error text prescribes for verbatim writes, would not actually escape it.
The @@ escape stays inline and guarded. This origin bit is the one
common-layer addition; it carries no domain logic.
# Conflicts:
#	internal/validate/path.go
#	internal/vfs/localfileio/path.go
@github-actions github-actions Bot added domain/ccm PR touches the ccm domain size/XL Architecture-level or global-impact change labels Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds safer confirmation and path handling, expands Sheets batch, style, read, schema, and flag behavior, introduces +styles-put, --writes, chart examples, and read-only spreadsheet analysis tools, and updates related schemas, generated metadata, tests, and documentation.

Changes

CLI infrastructure

Layer / File(s) Summary
Confirmation and path handling
internal/cmdutil/*, internal/validate/*, internal/vfs/localfileio/*
Adds shell-safe --yes retry hints, restricts absolute input paths to the system temp directory, and preserves relative-path fallback behavior.
Input-source tracking
shortcuts/common/*
Tracks whether flag values were resolved from stdin or @file inputs.
Batch validation and writes
shortcuts/sheets/batch_op_dispatch.go, lark_sheet_write_cells.go
Adds sub-operation key normalization, aggregated validation errors, multi-region --writes, matrix prechecks, and CSV path detection.
Styles pipeline
shortcuts/sheets/style_vocab.go, lark_sheet_workbook.go, lark_sheet_styles_put.go
Adds shared style normalization, border aliases, freeze parsing, style operation coalescing, and the new +styles-put shortcut.
Sheet operations and reads
shortcuts/sheets/lark_sheet_sheet_structure.go, lark_sheet_read_data.go, lark_sheet_table_io.go, read_output.go
Adds dimension-range batching, output-path handling, optional CSV ranges, character-limit controls, and truncation metadata.
Validation UX and examples
shortcuts/sheets/flag_ergonomics.go, flag_schema.go, flag_schema_validate.go, chart_examples.go
Adds intuitive aliases, richer schema errors, dotted schema paths, JSON normalization, and chart example printing.
Contracts and documentation
shortcuts/sheets/data/*, skills/lark-sheets/*
Updates flag definitions, schemas, generated metadata, workflow references, and new styles-put guidance.

Read-only analysis scripts

Layer / File(s) Summary
Spreadsheet analysis utilities
skills/lark-sheets/scripts/*
Adds A1 range utilities, a CLI wrapper, workbook inspection, table profiling, and subtable detection scripts.

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

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant CellsSet
    participant Validator
    participant BatchUpdate

    Caller->>CellsSet: Submit --writes payload
    CellsSet->>Validator: Normalize and validate write items
    Validator-->>CellsSet: Return set_cell_range operations
    CellsSet->>BatchUpdate: Submit one batch_update request
    BatchUpdate-->>Caller: Return result or validation error
Loading
sequenceDiagram
    participant Caller
    participant StylesPut
    participant StyleParser
    participant BatchUpdate

    Caller->>StylesPut: Submit --styles payload
    StylesPut->>StyleParser: Parse and normalize style items
    StyleParser-->>StylesPut: Return ordered operations
    StylesPut->>BatchUpdate: Submit expanded batch_update request
    BatchUpdate-->>Caller: Return execution result
Loading

Possibly related PRs

  • larksuite/cli#1337: Both changes update +csv-put path-guard behavior and coverage for resolved @file/stdin inputs.
  • larksuite/cli#1348: Both changes modify CSV path-guard validation expectations.

Suggested labels: feature

Suggested reviewers: liangshuo-1

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.54% 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 is concise and accurately summarizes the main themes of the PR.
Description check ✅ Passed The description covers the summary and test plan and is mostly aligned with the template, though the Changes and Related Issues sections are not explicit.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lark-sheets-develop

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.

Comment thread skills/lark-sheets/scripts/lark_detect_subtables.py Fixed
@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.89880% with 362 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.22%. Comparing base (358cd06) to head (a5032bb).

Files with missing lines Patch % Lines
shortcuts/sheets/lark_sheet_workbook.go 74.89% 41 Missing and 19 partials ⚠️
shortcuts/sheets/lark_sheet_styles_put.go 65.00% 45 Missing and 11 partials ⚠️
shortcuts/sheets/style_vocab.go 77.96% 26 Missing and 26 partials ⚠️
shortcuts/sheets/lark_sheet_sheet_structure.go 55.67% 35 Missing and 8 partials ⚠️
shortcuts/sheets/lark_sheet_write_cells.go 74.30% 26 Missing and 11 partials ⚠️
shortcuts/sheets/flag_schema.go 67.60% 17 Missing and 6 partials ⚠️
shortcuts/sheets/lark_sheet_batch_update.go 45.45% 13 Missing and 5 partials ⚠️
shortcuts/sheets/batch_op_dispatch.go 87.39% 10 Missing and 5 partials ⚠️
shortcuts/sheets/read_output.go 48.27% 15 Missing ⚠️
shortcuts/sheets/lark_sheet_table_io.go 70.96% 5 Missing and 4 partials ⚠️
... and 10 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2091      +/-   ##
==========================================
+ Coverage   75.16%   75.22%   +0.05%     
==========================================
  Files         912      916       +4     
  Lines       96475    97723    +1248     
==========================================
+ Hits        72517    73508     +991     
- Misses      18381    18563     +182     
- Partials     5577     5652      +75     

☔ 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.

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

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

🧩 Skill update

npx skills add larksuite/cli#feat/lark-sheets-develop -y -g

@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: 12

🧹 Nitpick comments (8)
skills/lark-sheets/scripts/lark_detect_subtables.py (1)

486-488: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Pass strict=False to the offset zip.

Ruff B905 flags the implicit default; the pairing here is intentionally ragged, so make that explicit.

♻️ Proposed tweak
-        for previous, current in zip(grid.row_numbers, grid.row_numbers[1:]):
+        for previous, current in zip(grid.row_numbers, grid.row_numbers[1:], strict=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 `@skills/lark-sheets/scripts/lark_detect_subtables.py` around lines 486 - 488,
Update the adjacent-row construction loop to call zip with strict=False, making
the intentionally ragged pairing explicit and resolving Ruff B905 while
preserving the existing previous/current mapping behavior.

Source: Linters/SAST tools

skills/lark-sheets/scripts/lark_profile_table.py (1)

130-138: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Make the zip pairing explicit.

row_numbers and values are built in lockstep by parse_annotated_csv, so strict=True both silences Ruff B905 and asserts that invariant.

♻️ Proposed tweak
-            for row_num, row in zip(grid.row_numbers, grid.values)
+            for row_num, row in zip(grid.row_numbers, grid.values, strict=True)
🤖 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-sheets/scripts/lark_profile_table.py` around lines 130 - 138,
Update the zip call in the first_non_empty computation to pass strict=True,
explicitly asserting the lockstep relationship between grid.row_numbers and
grid.values while preserving the existing filtering and return behavior.

Source: Linters/SAST tools

skills/lark-sheets/scripts/lark_inspect_workbook.py (1)

81-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Validate numeric args before the first network call.

--max-sheets is checked only after +workbook-info has already run, and --preview-rows isn't checked at all — --preview-rows 0 builds A1:<col>0, which only fails once +csv-get rejects it remotely. Validating both (plus --max-chars) at the top of inspect_workbook gives a local, actionable error.

♻️ Proposed reordering
 def inspect_workbook(args) -> tuple[dict[str, Any], list[str]]:
     warnings: list[str] = []
+    if args.max_sheets < 1:
+        raise LarkCliError("--max-sheets must be at least 1")
+    if args.preview_rows < 1:
+        raise LarkCliError("--preview-rows must be at least 1")
     workbook = envelope_data(
@@
-    if args.max_sheets < 1:
-        raise LarkCliError("--max-sheets must be at least 1")
     inspect_count = len(target_sheets)
🤖 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-sheets/scripts/lark_inspect_workbook.py` around lines 81 - 108,
Validate --max-sheets, --preview-rows, and --max-chars at the beginning of
inspect_workbook, before any workbook-info or other network calls. Reject values
below their supported minimums with local, actionable LarkCliError messages,
while preserving the existing later inspection behavior for valid arguments.
skills/lark-sheets/scripts/lark_sheet_read_cli.py (1)

116-130: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Annotate emit_error as NoReturn.

emit_error always terminates, but its -> None signature makes every caller's follow-up emit_success(ACTION, data, warnings) look like it uses a possibly-unbound data (see lark_detect_subtables.py Line 534-535, lark_inspect_workbook.py Line 163-164, lark_profile_table.py Line 513-514). NoReturn makes the control flow explicit to readers and type checkers.

♻️ Proposed annotation
-def emit_error(action: str, message: str, warnings: list[str] | None = None) -> None:
+def emit_error(action: str, message: str, warnings: list[str] | None = None) -> NoReturn:

Add from typing import Any, NoReturn to the imports on Line 8.

🤖 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-sheets/scripts/lark_sheet_read_cli.py` around lines 116 - 130,
Update the `emit_error` function annotation to return `NoReturn` instead of
`None`, and import `NoReturn` from `typing` alongside the existing typing
imports. Preserve its current error output and `sys.exit(1)` behavior.
shortcuts/sheets/flag_schema.go (1)

228-242: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

schemaChildKeys can under-report keys that schemaChild would actually resolve.

schemaChild falls back to oneOf branches when properties exists but lacks the segment; schemaChildKeys returns as soon as properties is found, so for a node carrying both properties and oneOf the "available keys" list omits resolvable branch keys.

♻️ Proposed tweak
 		if props, ok := m["properties"].(map[string]interface{}); ok {
 			for k := range props {
 				seen[k] = struct{}{}
 			}
-			return
 		}
 		if items, ok := m["items"]; ok {
 			collect(items, depth+1)
 			return
 		}
 		if branches, ok := m["oneOf"].([]interface{}); ok {
 			for _, b := range branches {
 				collect(b, depth+1)
 			}
 		}
🤖 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/sheets/flag_schema.go` around lines 228 - 242, Update
schemaChildKeys so finding a properties map does not return before collecting
keys from oneOf branches; preserve property-key collection and then recurse
through oneOf when present, matching schemaChild’s fallback resolution behavior.
shortcuts/sheets/flag_schema_validate_test.go (1)

673-780: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert typed metadata and cause preservation on these validateValueAgainstSchema error paths.

These four tests exercise the typed wrapper (sheetsValidationForFlag(...).WithCause(vErr)) but only match message substrings, so a regression that drops the --cells param, changes the subtype, or loses the wrapped cause would still pass. The file already has requireValidation for this; add a cause check too (e.g. errors.As on the unwrapped *typeMismatchError for the deep-mismatch case).

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."

🤖 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/sheets/flag_schema_validate_test.go` around lines 673 - 780,
Strengthen the four validateValueAgainstSchema tests to assert typed error
metadata, not only message text. Use the existing requireValidation helper and
errs.ProblemOf to verify category, subtype, and the --cells parameter for each
error path. Also verify cause preservation by unwrapping the returned error and
using errors.As to locate the underlying validation error, including
*typeMismatchError for the deep-mismatch case.

Source: Coding guidelines

shortcuts/sheets/flag_ergonomics_test.go (1)

494-606: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

All cases here use hyphenated flag spellings, so this suite doesn't exercise the underscore-variant gap noted in flag_ergonomics.go (Lines 204-216) — worth a follow-up case (e.g. --new_sheet_name) once that's fixed, but not a defect in this test as written.

🤖 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/sheets/flag_ergonomics_test.go` around lines 494 - 606, Extend
TestShortcuts_IntuitiveFlagHints with a follow-up case using an underscore-form
flag such as --new_sheet_name, once the underscore-variant handling in
flag_ergonomics.go is fixed. Assert the same curated hint and absence of
contradictory edit-distance suggestions as the corresponding hyphenated case.
shortcuts/sheets/helpers.go (1)

462-501: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

colorTheme bare-hex arrays are not covered by the normalizer.

style.colorTheme is documented in the schema as an array of #RRGGBB strings, but the walk only rewrites string values under color-keys; array elements ("colorTheme": ["4472C4", "ED7D31"]) fall through untouched and will hit the same server-side rejection the normalizer exists to prevent. Consider extending the array branch to rewrite bare-hex strings when the enclosing key is a color key.

🤖 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/sheets/helpers.go` around lines 462 - 501, Extend
normalizeChartHexColors to track whether the current value is under a color key,
and in the []interface{} branch prefix bare-hex string elements when that
context applies. Preserve recursive traversal and leave non-color arrays and
non-bare-hex values unchanged.
🤖 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 `@internal/cmdutil/confirm_test.go`:
- Around line 38-42: Strengthen confirmation contract coverage by extracting the
argument-dependent logic from RequireConfirmation into a testable helper such as
requireConfirmationFor(action string, args []string). Update the tests to assert
the complete “add --yes to confirm; re-run: …” hint for fixed arguments and the
plain hint when stdin arguments are supplied, so removing the retry branch fails
the tests.

In `@internal/cmdutil/confirm.go`:
- Around line 29-36: Update RequireConfirmation and the retry-command rendering
path to sanitize os.Args before embedding them in the WithHint message. Redact
values for sensitive flags such as --app-secret, --app-token, and --token in
both --flag value and --flag=value forms, while preserving non-sensitive
arguments and the existing retry behavior.

In `@internal/cmdutil/resolve.go`:
- Around line 88-95: Replace the os.ReadFile call in the resolved-path branch of
the input-reading function with internal/vfs.ReadFile, preserving the existing
error wrapping via wrapInputFileError and the SafeTempAbsInputPath validation
flow so injected or mocked filesystems can intercept the read.

In `@internal/vfs/localfileio/path.go`:
- Around line 152-170: Update absPathUnderTempDir to reject degenerate canonical
temp directories before resolving or accepting the input path, including when
filepath.EvalSymlinks(os.TempDir()) resolves to the filesystem root or otherwise
lacks a meaningful parent boundary. Preserve the existing canonical symlink
resolution and isUnderDir checks for valid, non-root temporary directories.

In `@shortcuts/sheets/batch_op_dispatch.go`:
- Around line 401-431: Guard the camelCase-to-kebab rewrite in the
key-normalization logic and the ranges-to-range unwrap branch against existing
canonical keys before assigning. Reuse the alias branch’s collision checks for
both the hyphenated and underscore forms, and return the established typed
validation error when a requested rewrite would overwrite an existing value;
preserve current rewrites when no canonical spelling is present.

In `@shortcuts/sheets/chart_examples_test.go`:
- Around line 34-42: Update the “unknown type lists available” test to assert
that the returned ValidationError’s Param equals "--print-example". Keep the
existing message-content assertion unchanged, using the ve value returned by
requireValidation.

In `@shortcuts/sheets/flag_ergonomics.go`:
- Around line 204-216: Normalize the parsed flag name by converting underscores
to hyphens before looking it up in intuitiveFlagHints within the curated hint
branch. Use the normalized name for the lookup while preserving the existing
hint, valid-flag list, and suggestions-clearing behavior, so underscore-spelled
habitual flags resolve to the same prescriptions as their hyphenated forms.

In `@shortcuts/sheets/read_output.go`:
- Around line 34-41: Honor an explicitly supplied max-chars value in
maxCharsInput instead of always returning the unbounded sentinel when
output-path is set; use the unbounded default only when max-chars was not
explicitly supplied, or reject the combination with typed validation. In
shortcuts/sheets/lark_sheet_read_data_test.go lines 37-52, add dry-run
assertions confirming explicit caps survive output-path handling. Update
skills/lark-sheets/references/lark-sheets-read-data.md lines 164-166, 183-185,
and 198-199 to document the chosen precedence or validation error.

In `@skills/lark-sheets/references/lark-sheets-chart.md`:
- Line 30: Update the +chart-create Flags table to include --print-example,
documenting that it accepts column, bar, line, area, pie, scatter, radar, or
combo and only prints the corresponding minimal template without creating a
chart. Keep the existing chart-create flag descriptions unchanged.

In `@skills/lark-sheets/references/lark-sheets-read-data.md`:
- Around line 93-94: Insert a blank line between the final table row and the
following list item in the markdown content, preserving the table and list text
unchanged.

In `@skills/lark-sheets/references/lark-sheets-styles-put.md`:
- Line 19: Update the freeze documentation in
skills/lark-sheets/references/lark-sheets-styles-put.md at lines 19-19 and
skills/lark-sheets/references/lark-sheets-workbook.md at lines 202-202 to state
that at least one of rows or cols must be greater than zero, while preserving
that zero or omission disables the respective dimension. Also update the
matching schema text at line 50 of the styles-put reference if it is regenerated
from the same source.

In `@tests/cli_e2e/sheets/sheets_sheet_shortcuts_dryrun_test.go`:
- Around line 258-290: Add live E2E tests alongside
TestSheets_DimInsertDryRunInheritAfterKeepsBeforePosition covering +dim-insert
and +dim-delete, including --inherit-style variants and +dim-delete --ranges.
Create the required spreadsheet/sheet data, execute the shortcuts with bot
credentials where applicable, assert the resulting sheet structure and styles,
and clean up all created resources; retain the existing dry-run payload test.

---

Nitpick comments:
In `@shortcuts/sheets/flag_ergonomics_test.go`:
- Around line 494-606: Extend TestShortcuts_IntuitiveFlagHints with a follow-up
case using an underscore-form flag such as --new_sheet_name, once the
underscore-variant handling in flag_ergonomics.go is fixed. Assert the same
curated hint and absence of contradictory edit-distance suggestions as the
corresponding hyphenated case.

In `@shortcuts/sheets/flag_schema_validate_test.go`:
- Around line 673-780: Strengthen the four validateValueAgainstSchema tests to
assert typed error metadata, not only message text. Use the existing
requireValidation helper and errs.ProblemOf to verify category, subtype, and the
--cells parameter for each error path. Also verify cause preservation by
unwrapping the returned error and using errors.As to locate the underlying
validation error, including *typeMismatchError for the deep-mismatch case.

In `@shortcuts/sheets/flag_schema.go`:
- Around line 228-242: Update schemaChildKeys so finding a properties map does
not return before collecting keys from oneOf branches; preserve property-key
collection and then recurse through oneOf when present, matching schemaChild’s
fallback resolution behavior.

In `@shortcuts/sheets/helpers.go`:
- Around line 462-501: Extend normalizeChartHexColors to track whether the
current value is under a color key, and in the []interface{} branch prefix
bare-hex string elements when that context applies. Preserve recursive traversal
and leave non-color arrays and non-bare-hex values unchanged.

In `@skills/lark-sheets/scripts/lark_detect_subtables.py`:
- Around line 486-488: Update the adjacent-row construction loop to call zip
with strict=False, making the intentionally ragged pairing explicit and
resolving Ruff B905 while preserving the existing previous/current mapping
behavior.

In `@skills/lark-sheets/scripts/lark_inspect_workbook.py`:
- Around line 81-108: Validate --max-sheets, --preview-rows, and --max-chars at
the beginning of inspect_workbook, before any workbook-info or other network
calls. Reject values below their supported minimums with local, actionable
LarkCliError messages, while preserving the existing later inspection behavior
for valid arguments.

In `@skills/lark-sheets/scripts/lark_profile_table.py`:
- Around line 130-138: Update the zip call in the first_non_empty computation to
pass strict=True, explicitly asserting the lockstep relationship between
grid.row_numbers and grid.values while preserving the existing filtering and
return behavior.

In `@skills/lark-sheets/scripts/lark_sheet_read_cli.py`:
- Around line 116-130: Update the `emit_error` function annotation to return
`NoReturn` instead of `None`, and import `NoReturn` from `typing` alongside the
existing typing imports. Preserve its current error output and `sys.exit(1)`
behavior.
🪄 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: 4219a54f-8599-4e64-84d4-fce10bfbdf25

📥 Commits

Reviewing files that changed from the base of the PR and between 358cd06 and d219d61.

📒 Files selected for processing (62)
  • internal/cmdutil/confirm.go
  • internal/cmdutil/confirm_test.go
  • internal/cmdutil/resolve.go
  • internal/validate/path.go
  • internal/vfs/localfileio/path.go
  • internal/vfs/localfileio/path_test.go
  • shortcuts/common/runner.go
  • shortcuts/common/runner_input_test.go
  • shortcuts/common/testing.go
  • shortcuts/sheets/batch_key_vocab_test.go
  • shortcuts/sheets/batch_op_contract_test.go
  • shortcuts/sheets/batch_op_dispatch.go
  • shortcuts/sheets/cells_set_writes_test.go
  • shortcuts/sheets/chart_examples.go
  • shortcuts/sheets/chart_examples_test.go
  • shortcuts/sheets/csv_put_guard_test.go
  • shortcuts/sheets/data/flag-defs.json
  • shortcuts/sheets/data/flag-schemas.json
  • shortcuts/sheets/flag_defs_gen.go
  • shortcuts/sheets/flag_defs_test.go
  • shortcuts/sheets/flag_ergonomics.go
  • shortcuts/sheets/flag_ergonomics_test.go
  • shortcuts/sheets/flag_schema.go
  • shortcuts/sheets/flag_schema_validate.go
  • shortcuts/sheets/flag_schema_validate_test.go
  • shortcuts/sheets/flag_schemas_gen.go
  • shortcuts/sheets/helpers.go
  • shortcuts/sheets/helpers_test.go
  • shortcuts/sheets/json_flag_normalize_test.go
  • shortcuts/sheets/lark_sheet_batch_update.go
  • shortcuts/sheets/lark_sheet_batch_update_test.go
  • shortcuts/sheets/lark_sheet_range_operations.go
  • shortcuts/sheets/lark_sheet_read_data.go
  • shortcuts/sheets/lark_sheet_read_data_test.go
  • shortcuts/sheets/lark_sheet_sheet_structure.go
  • shortcuts/sheets/lark_sheet_sheet_structure_test.go
  • shortcuts/sheets/lark_sheet_styles_put.go
  • shortcuts/sheets/lark_sheet_styles_put_test.go
  • shortcuts/sheets/lark_sheet_table_io.go
  • shortcuts/sheets/lark_sheet_workbook.go
  • shortcuts/sheets/lark_sheet_write_cells.go
  • shortcuts/sheets/read_output.go
  • shortcuts/sheets/sheet_ai_api.go
  • shortcuts/sheets/sheet_ai_api_flatten_test.go
  • shortcuts/sheets/shortcuts.go
  • shortcuts/sheets/style_vocab.go
  • shortcuts/sheets/styles_acceptance_test.go
  • shortcuts/sheets/styles_prescription_test.go
  • skills/lark-sheets/SKILL.md
  • skills/lark-sheets/references/lark-sheets-batch-update.md
  • skills/lark-sheets/references/lark-sheets-chart.md
  • skills/lark-sheets/references/lark-sheets-read-data.md
  • skills/lark-sheets/references/lark-sheets-sheet-structure.md
  • skills/lark-sheets/references/lark-sheets-styles-put.md
  • skills/lark-sheets/references/lark-sheets-workbook.md
  • skills/lark-sheets/references/lark-sheets-write-cells.md
  • skills/lark-sheets/scripts/lark_detect_subtables.py
  • skills/lark-sheets/scripts/lark_inspect_workbook.py
  • skills/lark-sheets/scripts/lark_profile_table.py
  • skills/lark-sheets/scripts/lark_sheet_range.py
  • skills/lark-sheets/scripts/lark_sheet_read_cli.py
  • tests/cli_e2e/sheets/sheets_sheet_shortcuts_dryrun_test.go

Comment thread internal/cmdutil/confirm_test.go
Comment thread internal/cmdutil/confirm.go
Comment thread internal/cmdutil/resolve.go
Comment thread internal/vfs/localfileio/path.go
Comment thread shortcuts/sheets/batch_op_dispatch.go
Comment thread shortcuts/sheets/read_output.go
Comment thread skills/lark-sheets/references/lark-sheets-chart.md
Comment thread skills/lark-sheets/references/lark-sheets-read-data.md
Comment thread skills/lark-sheets/references/lark-sheets-styles-put.md Outdated
Comment on lines +258 to +290

func TestSheets_DimInsertDryRunInheritAfterKeepsBeforePosition(t *testing.T) {
setSheetsDryRunEnv(t)

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
t.Cleanup(cancel)

result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{
"sheets", "+dim-insert",
"--spreadsheet-token", "shtDryRun",
"--sheet-id", "sheet1",
"--position", "D",
"--count", "1",
"--inherit-style", "after",
"--dry-run",
},
DefaultAs: "user",
})
require.NoError(t, err)
result.AssertExitCode(t, 0)

out := result.Stdout
require.Equal(t, "/open-apis/sheet_ai/v2/spreadsheets/shtDryRun/tools/invoke_write", clie2e.DryRunGet(out, "api.0.url").String(), "stdout:\n%s", out)
require.Equal(t, "modify_sheet_structure", clie2e.DryRunGet(out, "tool_name").String(), "stdout:\n%s", out)
require.Equal(t, "insert", clie2e.DryRunGet(out, "tool_input.operation").String(), "stdout:\n%s", out)
// inherit-style=after copies the following column's style via a plain
// before-insert at the same position (the backend anchors on the following
// column), so position stays D with side=before — the blank lands before D.
require.Equal(t, "D", clie2e.DryRunGet(out, "tool_input.position").String(), "stdout:\n%s", out)
require.Equal(t, int64(1), clie2e.DryRunGet(out, "tool_input.count").Int(), "stdout:\n%s", out)
require.Equal(t, "before", clie2e.DryRunGet(out, "tool_input.side").String(), "inherit-style=after copies the following-side style via side=before; stdout:\n%s", out)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n --type=go -C2 'DimInsert|DimDelete|dim-insert|dim-delete' tests/cli_e2e/sheets | rg -n 'SkipWithoutUserToken|bot|DefaultAs.*bot'

Repository: larksuite/cli

Length of output: 151


🏁 Script executed:

#!/bin/bash
set -e

echo "Files:"
fd -a 'sheets.*_test\.go|.*_sheet.*test\.go' tests/cli_e2e 2>/dev/null | sed 's#^\./##' | sort

echo
echo "DimInsert/DimDelete/dim-insert/dim-delete references in tests/cli_e2e (context):"
rg -n --type=go -C3 'DimInsert|DimDelete|dim-insert|dim-delete|DimDelete|insert_dimensions|delete_dimensions|inherit-style|ranges' tests/cli_e2e || true

echo
echo "DryRunGet/usages of defaultAs bot in sheets tests:"
rg -n --type=go -C2 'DryRunGet|DefaultAs|SkipWithoutUserToken|bot' tests/cli_e2e/sheets || true

echo
echo "Target file lines 220-310:"
sed -n '220,310p' tests/cli_e2e/sheets/sheets_sheet_shortcuts_dryrun_test.go

Repository: larksuite/cli

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -e

echo "Target file context around all dim shortcuts tests:"
rg -n -C8 'func TestSheets_Dim(Insert|Delete)|+dim-(insert|delete)|inherit-style|--ranges|--position|--side|ToolInput|tool_input|DryRunGet' tests/cli_e2e/sheets || true

Repository: larksuite/cli

Length of output: 452


🏁 Script executed:

#!/bin/bash
set -e

echo "Search only for dim shortcuts in tests/cli_e2e:"
rg -n -C4 'dim-(insert|delete)|DimInsert|DimDelete|--inherit-style|--ranges|--position|--side|modify_sheet_structure|tool_input' tests/cli_e2e || true

echo
echo "Search workflow files under tests/cli_e2e/sheets only for dim shortcuts:"
rg -n -C4 'dim-(insert|delete)|DimInsert|DimDelete|inherit-style|ranges' tests/cli_e2e/sheets || true

Repository: larksuite/cli

Length of output: 11026


Add live E2E coverage for the dim shortcut behavior changes.

TestSheets_DimInsertDryRunInheritAfterKeepsBeforePosition only inspects a dry-run payload. tests/cli_e2e/**/*.go requires live E2E coverage for new flows or behavior changes with create/use/cleanup and bot credentials where applicable, and there is no workflow test exercising +dim-insert/+dim-delete with these flags or +dim-delete --ranges.

🤖 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/sheets/sheets_sheet_shortcuts_dryrun_test.go` around lines 258
- 290, Add live E2E tests alongside
TestSheets_DimInsertDryRunInheritAfterKeepsBeforePosition covering +dim-insert
and +dim-delete, including --inherit-style variants and +dim-delete --ranges.
Create the required spreadsheet/sheet data, execute the shortcuts with bot
credentials where applicable, assert the resulting sheet structure and styles,
and clean up all created resources; retain the existing dry-run payload test.

Source: Path instructions

…tch and read caps

Review fixes from CodeRabbit / code-quality on PR #2091:

- cmdutil: route the @/tmp fast-path read through vfs.ReadFile so fakes
  keep intercepting it; suppress the confirmation retry line when argv
  carries a credential-bearing flag (secrets stay out of error envelopes);
  extract requireConfirmationFor and pin the composed hint in tests
- localfileio: reject a degenerate TMPDIR (e.g. "/") so the temp-dir
  read exception cannot widen into accepting arbitrary absolute paths
- sheets batch dispatch: duplicate canonical + variant input keys
  (sheetName/sheet_name, ranges/range) now reject with a prescriptive
  error instead of silently overwriting the canonical value
- sheets flag ergonomics: curated unknown-flag hints match underscore
  spellings (--frozen_rows hits the --frozen-rows entry)
- sheets read offload: an explicit --max-chars survives --output-path
  instead of being replaced by the unbounded sentinel
- chart: declare --print-example in flag-defs.json (single source with
  the generated reference tables) instead of imperative registration;
  assert the validation Param in the unknown-type test
- e2e: add live +dim-insert / +dim-delete workflow coverage (insert
  lands before position, --range delete, atomic scattered --ranges)
- docs (synced from sheet-skill-spec): +chart-create flags table gains
  --print-example, freeze docs state the at-least-one-positive rule,
  read-data table gets its MD058 blank line, detect-subtables explains
  its fallback except

@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: 1

🤖 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 `@tests/cli_e2e/sheets/sheets_dim_workflow_test.go`:
- Around line 80-100: The dim-insert workflow currently covers only the before
inheritance mapping. Add a corresponding test case using “--inherit-style after”
in the existing dim-insert test flow, and directly assert that column B remains
blank while the original B and C values shift right, matching the expected A1:D1
output.
🪄 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: fa78106f-171b-494c-b70a-44502981829e

📥 Commits

Reviewing files that changed from the base of the PR and between d8f6154 and a5032bb.

📒 Files selected for processing (22)
  • internal/cmdutil/confirm.go
  • internal/cmdutil/confirm_test.go
  • internal/cmdutil/resolve.go
  • internal/vfs/localfileio/path.go
  • shortcuts/sheets/batch_key_vocab_test.go
  • shortcuts/sheets/batch_op_dispatch.go
  • shortcuts/sheets/chart_examples.go
  • shortcuts/sheets/chart_examples_test.go
  • shortcuts/sheets/data/flag-defs.json
  • shortcuts/sheets/data/flag-schemas.json
  • shortcuts/sheets/flag_defs_gen.go
  • shortcuts/sheets/flag_ergonomics.go
  • shortcuts/sheets/flag_ergonomics_test.go
  • shortcuts/sheets/lark_sheet_read_data_test.go
  • shortcuts/sheets/read_output.go
  • skills/lark-sheets/references/lark-sheets-chart.md
  • skills/lark-sheets/references/lark-sheets-read-data.md
  • skills/lark-sheets/references/lark-sheets-styles-put.md
  • skills/lark-sheets/references/lark-sheets-workbook.md
  • skills/lark-sheets/references/lark-sheets-write-cells.md
  • skills/lark-sheets/scripts/lark_detect_subtables.py
  • tests/cli_e2e/sheets/sheets_dim_workflow_test.go
🚧 Files skipped from review as they are similar to previous changes (19)
  • internal/vfs/localfileio/path.go
  • shortcuts/sheets/chart_examples_test.go
  • skills/lark-sheets/references/lark-sheets-styles-put.md
  • internal/cmdutil/confirm.go
  • shortcuts/sheets/chart_examples.go
  • shortcuts/sheets/batch_key_vocab_test.go
  • internal/cmdutil/resolve.go
  • skills/lark-sheets/references/lark-sheets-workbook.md
  • shortcuts/sheets/flag_ergonomics.go
  • shortcuts/sheets/read_output.go
  • skills/lark-sheets/references/lark-sheets-read-data.md
  • skills/lark-sheets/scripts/lark_detect_subtables.py
  • shortcuts/sheets/flag_ergonomics_test.go
  • shortcuts/sheets/batch_op_dispatch.go
  • skills/lark-sheets/references/lark-sheets-write-cells.md
  • shortcuts/sheets/lark_sheet_read_data_test.go
  • shortcuts/sheets/data/flag-schemas.json
  • shortcuts/sheets/data/flag-defs.json
  • shortcuts/sheets/flag_defs_gen.go

Comment on lines +80 to +100
t.Run("dim-insert inherit-style before lands before position", func(t *testing.T) {
result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{
"sheets", "+dim-insert",
"--spreadsheet-token", spreadsheetToken,
"--sheet-id", sheetID,
"--position", "B",
"--count", "1",
"--inherit-style", "before",
},
DefaultAs: "bot",
})
require.NoError(t, err)
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)

// Old columns B/C shift right; the blank column sits at B.
out := readRow1(t, "A1:D1")
assert.Contains(t, out, "r1c1,,r1c2,r1c3",
"inserted blank column must land before position B; stdout:\n%s", out)
})

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

Cover the --inherit-style after mapping.

Line 80 only exercises before, although the test rationale identifies after as the regression direction. Add an after case asserting that insertion still leaves column B blank and shifts the original B/C values right; otherwise an after mapping regression passes this workflow. As per coding guidelines, “Every behavior change must have an accompanying test, and contract tests must assert the changed field or behavior directly.”

🤖 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/sheets/sheets_dim_workflow_test.go` around lines 80 - 100, The
dim-insert workflow currently covers only the before inheritance mapping. Add a
corresponding test case using “--inherit-style after” in the existing dim-insert
test flow, and directly assert that column B remains blank while the original B
and C values shift right, matching the expected A1:D1 output.

Source: Coding guidelines

if argReadsStdin(a) || argCarriesSecret(a) {
return ""
}
parts = append(parts, shellQuoteArg(a))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] Avoid echoing free-form payloads into confirmation errors

The guard only detects credential-looking flag names, but high-risk commands also accept sensitive free-form values such as apps +db-execute --sql and base +form-submit --json. A short inline password, SQL literal, or PII payload is therefore copied into the confirmation envelope, stderr, and downstream logs. Please avoid rendering arbitrary argv values here—either whitelist known-safe flags or fall back to the generic add --yes hint for payload-bearing invocations.

// only the listed failures need resending.
if strings.Contains(detail.Message, "succeeded") &&
!strings.Contains(detail.Message, " 0 succeeded") {
if len(detail.Failures) == 1 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] Do not infer fail-fast execution from a single failure

A single failure does not imply fail-fast: +batch-update --continue-on-error can report one failed operation while later operations have already succeeded. Prescribing operations[i:] in that case replays those later successes and can double-apply inserts, deletes, or moves. Please pass the execution mode into this formatter, or emit recovery guidance that distinguishes failed operations from the not-run tail.


## 使用场景

写入。对存量表格的多个子表批量应用视觉规格:新表美化、加汇总行后统一版式、按分组合并同类单元格、调列宽行高、冻结表头。整份规格展开为一次原子批量提交,全部生效或全部不生效;纯样式盖章可安全重放(同一份规格重发无副作用)。

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] Stop promising transactional batch semantics

+styles-put is implemented with the same batch_update path that this PR documents as fail-fast without rollback. If operations for an earlier sheet succeed and a later sheet is missing or otherwise fails, the earlier changes remain applied, contradicting “all or nothing” and making whole-spec retries unsafe. Please replace the atomic/all-or-nothing wording here and in the matching help for the new batch-backed helpers with explicit no-rollback and recovery semantics.

continue
}
seenName[name] = true
payload, itemProbs := parseWorkbookCreateStyleItem(item, path)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Reject unknown styles item keys before expansion

parseWorkbookCreateStyleItem ignores unrecognized top-level keys. For example, an item containing valid cell_styles plus freezee: {"rows":1} succeeds, applies the styles, and silently drops the requested freeze. Please reject keys outside name, cell_styles, row_sizes, col_sizes, cell_merges, and freeze, ideally with a did-you-mean hint.

"input": map[string]interface{}{
"excel_id": token,
"sheet_name": spec.name,
"range": stripSheetPrefix(cs.Range),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Validate sheet-prefixed ranges before stripping them

This silently discards the range’s sheet prefix without checking it against spec.name. An item with name: "Summary" and range: "Detail!A1:D1" is therefore applied to Summary!A1:D1 and exits successfully. Please reject prefixed ranges or require the prefix to match the item’s target sheet before removing it.

return err
}
b = append(b, '\n')
if _, err := runtime.FileIO().Save(path, fileio.SaveOptions{}, bytes.NewReader(b)); err != nil {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Preserve typed errors for read-output file failures

Returning the raw FileIO.Save error causes an unsafe path such as --output-path ../../outside.json to fall through as internal/unknown instead of the required validation error, while write failures also lose the file_io subtype. Please return common.WrapSaveErrorTyped(err) here and add assertions for the typed category, subtype, and preserved cause.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/ccm PR touches the ccm domain size/XL Architecture-level or global-impact change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants