Skip to content

feat(sheets): typed table I/O & error contract, workbook import/export, skill refresh#1355

Merged
xiongyuanwen-byted merged 92 commits into
mainfrom
feat/lark-sheets-develop
Jun 25, 2026
Merged

feat(sheets): typed table I/O & error contract, workbook import/export, skill refresh#1355
xiongyuanwen-byted merged 92 commits into
mainfrom
feat/lark-sheets-develop

Conversation

@xiongyuanwen-byted

@xiongyuanwen-byted xiongyuanwen-byted commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Lands the accumulated feat/lark-sheets-develop work into main (337 commits relative to local main, 57 truly branch-only). The thrust is typed, type-faithful batch I/O for Lark Sheets plus a domain-wide migration to the typed error contract, with broad lark-sheets skill/doc refresh from the upstream spec.

Changes

New shortcuts & capabilities (feat)

  • Typed table I/O: +table-put / +table-get with column type & number-format preservation (lark_sheet_table_io.go).
  • Typed write protocol: pandas-DataFrame-shaped --sheets JSON accepted by +table-put / +workbook-create (pandas DataFrame ↔ payload helper at skills/lark-sheets/scripts/sheets_df.py).
  • One-step typed write with styling: +table-put --styles (cell_styles / cell_merges / row_sizes / col_sizes).
  • +workbook-create rework: typed --sheets + --styles (deep-merge cell/border styles, manual border validation); old --headers / --header-style removed (subsumed).
  • Workbook import/export: +workbook-import wraps the drive import core; +workbook-export refactored to reuse the drive export core (inherits resume/cancel/sanitize/status labels).
  • Gridline shortcuts: +sheet-show-gridline / +sheet-hide-gridline.
  • Wiki URLs: --url / --spreadsheet-token accept /wiki/<node_token> (resolved at Execute, Validate/DryRun stays network-free).
  • Chart: aggregateType gains counta (non-empty count incl. text).
  • Error hygiene & ergonomics: --token alias for --spreadsheet-token; write-op error messages carry actionable hints (--print-schema, stdin fallback); stdin single-input constraint surfaced in flag help.
  • Style field aliases: styles accepts halign / valign / horizontal_align / vertical_align as aliases for the canonical alignment fields.

Notable removal

  • Arrow IPC --dataframe / --dataframe-out channel + apache/arrow/go/v17 dep: an earlier revision of this branch added an Arrow IPC binary input/output channel to +table-put / +table-get / +workbook-create, plus a CI go-licenses --ignore github.com/apache/arrow/go/v17 exemption. It was removed before merge (commit 9cd27855): the JSON --sheets path already covers everything pandas users need, and the column-major / zero-copy benefits collapse the moment the CLI re-encodes into the row-oriented sheets OpenAPI JSON body. Pandas users keep the round-trip via skills/lark-sheets/scripts/sheets_df.py over the typed JSON path. Pre-removal state preserved on branch feat/sheets-arrow-stash.

Robustness & typed error contract (refactor / fix)

  • Sheets domain migrated to the typed error contract: common.FlagErrorfcommon.ValidationErrorf; +table-put partial-success ported off legacy output.ExitError to runtime.OutPartialFailure; file-I/O and tool-call errors classified.
  • Unified partial-state contract for +workbook-create: when the spreadsheet POST succeeds but the follow-up initial fill fails, the result now emits the same OutPartialFailure envelope shape as +table-put's partial write (ok:false stdout envelope with spreadsheet_token + reason + hint + structured cause), instead of the previous failed_precondition stderr envelope — so agents see one consistent "side effect landed but follow-up didn't" shape across the domain.
  • +table-get now defaults to the full used range (probed over the physical grid) instead of the A1 current_region — fixes silent truncation past internal blank rows; append mode's probe gets the same fix.
  • Conditional-format attrs validated by rule_type (cross-field), with a schema-drift guard test — closes a class of dirty payloads that crashed the frontend on snapshot deserialization.
  • +pivot-create: --target-position and --range are mutually exclusive (both map to properties.range).
  • set_cell_range mention_type constrained to the proto MENTION_FILE_TYPE enum.
  • +cells-get / +csv-get --max-chars default raised 200000 → 500000.

Skill / docs (synced from sheet-skill-spec)

  • set+H quoting & guidance generalized; cheat-sheet adds hallucinated commands; +workbook-import routing.
  • +csv-put formula-with-comma example & RFC-4180 escaping; structured-write preference surfaced at the decision point.
  • --max-chars exposed as a visible flag with "spill to file" guidance for large reads.
  • Cell-image vs float-image routing clarified by record-binding.
  • read-data cross-checks +sheet-info for hidden rows/cols and +workbook-info for physical row_count.
  • Drops over-restrictive caveats ("Feishu sheets only" / "not for local Excel") now that import/export close the loop.
  • lark-sheets skill version bumped to 3.0.0.

Test Plan

  • Unit tests pass — go test -race ./cmd/... ./internal/... ./shortcuts/... ./extension/...
  • go build ./..., golangci-lint run --new-from-rev=origin/main (0 issues), gofmt clean
  • Dry-run E2E for +table-put --styles (tests/cli_e2e/sheets/sheets_table_put_dryrun_test.go)
  • Live E2E for wiki-URL resolution (+workbook-info, +cells-get, +csv-get, +sheet-create, +sheet-rename, +csv-put)
  • Dry-run + live E2E for +sheet-{show,hide}-gridline, +workbook-import, +workbook-export
  • Full e2e suite (tests/cli_e2e) — not run locally (requires live credentials)

Related Issues

  • None

Summary by CodeRabbit

  • New Features

    • Typed table read/write: +table-get and +table-put.
    • Sheet gridline toggles: +sheet-show-gridline and +sheet-hide-gridline.
    • Workbook import shortcut and typed --sheets support for workbook creation.
  • Enhancements

    • Typed --sheets one-step workbook create+write; workbook export/import have clearer dry-run/execute/resume behaviors.
    • CSV input now treats =-prefixed cells as formulas; csv-get row-prefix handling clarified; empty output-dir normalizes to cwd.
  • Removals

    • Removed +csv-get --rows-json (use +table-get).
  • Documentation

    • Added financial-modeling standards and expanded sheets references.

zhengzhijiej-tech and others added 21 commits June 4, 2026 17:00
feat(sheets): add gridline show/hide shortcuts
…tfalls

Add targeted guidance to six lark-sheets references to reduce frequent
mistakes when editing spreadsheets through the CLI:

- write-cells: sanity-check units / dimension conversion / quantity factors
  before formula writes (formulas can run clean yet be off by a factor);
  keep derived output off original data columns to avoid clobbering source
- core-operations: prefer live formulas for derived values even when "live
  update" is not explicitly requested; scope rewrite/transform precisely so
  rows/columns that should stay unchanged are kept 1:1; treat header-stated
  format rules as checklist items; confirm the artifact file actually exists
  before finishing; write back bare values from local scripts
- visual-standards: apply border/header formatting on explicit request and
  identify the real header row; keep font size consistent with the source
- range-operations: keep total column width within A4 for printing
- read-data: dedup/compare long numbers via raw values, not csv formatted
  display (scientific notation collapses distinct numbers and causes false
  duplicates)
- chart: format date/number axes via source-cell number_format; place charts
  outside the data area so they do not cover existing data
- Add lark_sheet_table_io.go with +table-put / +table-get and tests
- Refactor read-data; extend workbook; register new shortcuts
- Sync generated flag defs/schemas (go:embed) from sheet-skill-spec
- Sync skill references (write-cells numeric-column guidance, plus
  read-data / workbook / chart updates)
Quick-ref table (SKILL.md, the first decision point) had no +table-put and
gated typed writes on "DataFrame", so a model holding a Counter/list/dict
would fall back to +csv-put and silently lose number/date fidelity.

- split csv-put row to plain-text values (no numeric/date semantics)
- add +table-put row for typed writes into an existing sheet
- add +workbook-create --sheets row for create + typed write in one shot
- add judgment note: number/amount/date/percent/count -> +table-put
  (or +workbook-create --sheets when the workbook does not exist yet);
  plain text -> +csv-put
- reframe write-cells scenario row to lead with numeric semantics
- point new-table writes at +workbook-create --sheets (one shot) instead
  of the create-empty-then-table-put two-step

Synced from sheet-skill-spec canonical (generate:cli + sync:cli).
Mirror the upstream sheet-skill-spec change removing the "not applicable to local Excel files" tail from the sheets skill and reference descriptions.
Mirror the upstream sheet-skill-spec change removing the "applies to Feishu sheets only" tail from the 14 sheet reference descriptions.
Import a local xlsx/xls/csv as a new spreadsheet by delegating to the shared drive import flow with the target type pinned to sheet. Refactor drive +import to expose ImportParams / ValidateImport / PlanImportDryRun / RunImport (behavior unchanged, existing drive tests still cover it); sheets reuses them. Regenerate flag_defs_gen.go and sync the spec mirror.
Replace +workbook-export's parallel export-task implementation with the shared drive ExportParams/RunExport core (pinned to type=sheet). Drops ~90 lines of duplicated poll/download code; +workbook-export now inherits drive's ctx cancellation, resume-on-timeout, filename sanitize/overwrite, and the full set of export status labels. The output contract aligns with drive's (adds ready/downloaded/doc_type; saved_path preserved). Also normalize an empty drive --output-dir to "." so drive +export behavior is unchanged, and fix the sheets export e2e to call +workbook-export instead of a nonexistent +export.
…ested metric

- range-operations: only widen new / overflowing columns; never recompute or
  shrink the widths of existing columns (any blanket resize, even by 1px,
  breaks the original visual format)
- chart: when the user asks for a share / percentage, the value axis should be
  a percentage (pie, or stack.percentage on bar/column) rather than raw counts
Replace scoring-framework wording in the examples with plain functional
consequences (e.g. "not delivered", "goes stale when the source changes",
"breaks the original visual format"), so the references stay agent-facing.
Bring the hand-applied write-cells example in line with the spec-generated
reference so the CLI mirror is byte-identical to the canonical source.
docs(sheets): strengthen lark-sheets references for common editing pitfalls
Sync the formula-support wording from sheet-skill-spec (flag-defs, skill
references) and update the hand-authored cobra Description and comment for
+csv-put. +csv-put evaluates a leading-= cell as a formula via
set_range_from_csv; descriptions only, no behavior change.
The chart reference's placement example used non-existent flags
--dimension/--start/--end for +dim-insert. The real signature is
--position (required) + --count (required); copying the example
fails Validate with "--position is required". Replace it with
+dim-insert --position V --count 6 (insert 6 columns before V,
i.e. after U), aligning with the sheet-structure reference.
Sync three reference-doc corrections from the spec source:

1. chart: label position.row as 0-based (first row = row:0), distinct
   from the 1-based row numbers used by A1 ranges and +dim-insert
   --position, removing the row-base ambiguity.

2. chart: convert the three runnable examples whose JSON contains a
   quoted sheet prefix ('Sheet1'!A1) from inline single-quoted
   --properties '{...}' to a stdin heredoc (--properties - <<'JSON').
   Inside an inline single-quoted string bash strips the inner quotes
   around the sheet name (and splits names with spaces into words),
   corrupting the JSON; a quoted heredoc delimiter performs no shell
   substitution and preserves it. Adds a short note on the pitfall.

3. filter / filter-view: add the full conditions[].type x compare_type
   enum table (text / number / multiValue / color and their respective
   compare_type values and values shape), and call out the
   equals/notEquals (with s) vs equal/notEqual (no s) gotcha. The docs
   previously only showed two values via examples.
The base flag description for +sheet-create's --index omitted the
coordinate base, while its siblings +sheet-move ("Target position
(0-based)") and +sheet-copy already state 0-based. Align the description
so the index base is unambiguous. Synced from the spec source
(flag-defs.json + workbook reference).
docs(sheets): chart / filter / workbook reference corrections
@CLAassistant

CLAassistant commented Jun 9, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions Bot added domain/ccm PR touches the ccm domain size/XL Architecture-level or global-impact change labels Jun 9, 2026
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a typed table protocol (TablePut/TableGet), centralizes Drive export/import into exported helpers, integrates those cores into workbook shortcuts (create/export/import), removes CsvGet's rows-json path, updates CLI flag/schema/dispatch metadata, and adds tests and documentation for the new contracts.

Changes

Sheets Typed Table Protocol & Workbook Integration

Layer / File(s) Summary
Drive Export/Import Core Extraction
shortcuts/drive/drive_export.go, shortcuts/drive/drive_import.go, shortcuts/drive/drive_export_test.go
ExportParams/ImportParams structs and exported helpers (ValidateExport, PlanExportDryRun, RunExport, etc.) encapsulate validation, planning, and execution logic; --output-dir "" is normalized to ".". Tests cover empty --output-dir download behavior.
Typed Table Protocol (TablePut & TableGet)
shortcuts/sheets/lark_sheet_table_io.go, shortcuts/sheets/lark_sheet_table_io_test.go
Implements typed table read/write: payload parsing, validation, typed cell conversion (ISO date ↔ serial), matrix building, batched set_cell_range writes, append/overwrite modes, partial-failure envelopes, and comprehensive unit tests plus dry-run behavior for writes and reads.
Workbook Operations Refactoring & Typed Sheet Creation
shortcuts/sheets/lark_sheet_workbook.go, shortcuts/sheets/lark_sheet_workbook_export_test.go, shortcuts/sheets/lark_sheet_workbook_import_test.go
WorkbookExport/WorkbookImport now delegate to drive core helpers; WorkbookCreate gains a typed --sheets mode (XOR with --values) that uses the TablePut payload parsing/write helpers. Tests cover export-only behavior, import type pinning, and typed create flows.
Sheet Visibility Shortcuts & Batch Operations
shortcuts/sheets/lark_sheet_workbook.go, shortcuts/sheets/batch_op_dispatch.go, shortcuts/sheets/batch_op_contract_test.go, shortcuts/sheets/data/flag-defs.json, shortcuts/sheets/flag_defs_gen.go
Adds +sheet-show-gridline and +sheet-hide-gridline, integrates them into batch dispatch, and adds dry-run contract tests.
CSV Refinements & Flag Schema Updates
shortcuts/sheets/lark_sheet_read_data.go, shortcuts/sheets/lark_sheet_read_data_test.go, shortcuts/sheets/lark_sheet_write_cells.go, shortcuts/sheets/data/flag-defs.json, shortcuts/sheets/data/flag-schemas.json, shortcuts/sheets/flag_defs_gen.go, shortcuts/sheets/flag_schemas_gen.go, shortcuts/sheets/shortcuts.go, tests/cli_e2e/sheets/sheets_crud_workflow_test.go
Removes --rows-json from CsvGet and adds --include-row-prefix; documents CsvPut formula semantics; updates generated flag metadata and schemas to add +table-get/+table-put/+workbook-import and WorkbookCreate --sheets. Updates e2e test to call sheets +workbook-export.
Object CRUD & Pivot Mutex
shortcuts/sheets/lark_sheet_object_crud.go, shortcuts/sheets/lark_sheet_object_crud_test.go
Adds validateCreateInput hook and maps --target-position/--range into properties.range with a validation mutex for conflicting inputs; adds tests covering the mutex behavior.
Skills & Reference Documentation
skills/lark-sheets/SKILL.md, skills/lark-sheets/references/*.md
Adds a financial-modeling standards doc and updates various sheets references to document typed workflows, new shortcuts, CSV/formula semantics, and guardrails for write operations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • larksuite/cli#194: Refactors Drive export/import logic touching shortcuts/drive/drive_export.go and drive_import.go.
  • larksuite/cli#1205: Overlaps on Drive export error handling and exported flow changes.
  • larksuite/cli#1264: Related to adding sheet gridline shortcuts and dispatch/contract wiring.

Suggested labels

feature

Suggested reviewers

  • caojie0621
  • zhengzhijiej-tech
  • kongenpei

Poem

"🐇 I hopped through rows and typed each cell,
dates turned to serials and back so well.
TablePut wrote, TableGet read true,
workbooks export, imports stitch through.
A carrot-coded cheer for sheets anew!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.70% 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
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.
Title check ✅ Passed The title clearly summarizes the main changes: typed table I/O, workbook import/export, and skill refresh for the Lark Sheets domain.
Description check ✅ Passed The PR description matches the required template with Summary, Changes, Test Plan, and Related Issues filled out.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 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.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

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

🧩 Skill update

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

@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.27443% with 418 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.19%. Comparing base (d11a6e9) to head (9534e33).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
shortcuts/sheets/lark_sheet_workbook.go 68.77% 122 Missing and 71 partials ⚠️
shortcuts/sheets/lark_sheet_table_io.go 84.37% 70 Missing and 52 partials ⚠️
shortcuts/drive/drive_export.go 83.83% 24 Missing and 8 partials ⚠️
shortcuts/sheets/lark_sheet_object_crud.go 70.49% 12 Missing and 6 partials ⚠️
shortcuts/drive/drive_import.go 84.26% 7 Missing and 7 partials ⚠️
shortcuts/sheets/helpers.go 84.94% 8 Missing and 6 partials ⚠️
shortcuts/sheets/lark_sheet_sheet_structure.go 14.28% 6 Missing ⚠️
shortcuts/sheets/lark_sheet_range_operations.go 28.57% 5 Missing ⚠️
shortcuts/sheets/lark_sheet_write_cells.go 28.57% 4 Missing and 1 partial ⚠️
shortcuts/sheets/lark_sheet_batch_update.go 40.00% 3 Missing ⚠️
... and 4 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1355      +/-   ##
==========================================
+ Coverage   74.03%   74.19%   +0.16%     
==========================================
  Files         787      789       +2     
  Lines       76328    77614    +1286     
==========================================
+ Hits        56509    57587    +1078     
- Misses      15572    15666      +94     
- Partials     4247     4361     +114     

☔ 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: 4

Caution

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

⚠️ Outside diff range comments (1)
shortcuts/sheets/data/flag-schemas.json (1)

17-68: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add gridline shortcuts to +batch-update.operations.shortcut enum.

+sheet-show-gridline and +sheet-hide-gridline are now batchable in dispatch/tests, but they’re missing from the schema enum, so schema-validated +batch-update payloads can be rejected incorrectly.

Suggested fix
               "enum": [
                 "+cells-set",
                 "+cells-set-style",
                 "+cells-clear",
                 "+cells-merge",
                 "+cells-unmerge",
                 "+cells-replace",
                 "+csv-put",
                 "+dropdown-set",
                 "+dim-insert",
                 "+dim-delete",
                 "+dim-hide",
                 "+dim-unhide",
                 "+dim-freeze",
                 "+dim-group",
                 "+dim-ungroup",
                 "+rows-resize",
                 "+cols-resize",
                 "+range-move",
                 "+range-copy",
                 "+range-fill",
                 "+range-sort",
                 "+sheet-create",
                 "+sheet-delete",
                 "+sheet-rename",
                 "+sheet-move",
                 "+sheet-copy",
                 "+sheet-hide",
                 "+sheet-unhide",
                 "+sheet-set-tab-color",
+                "+sheet-show-gridline",
+                "+sheet-hide-gridline",
                 "+chart-create",
                 "+chart-update",
                 "+chart-delete",
                 "+pivot-create",
                 "+pivot-update",
🤖 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/data/flag-schemas.json` around lines 17 - 68, The enum for
batch-update operation shortcuts is missing the new gridline actions; update the
enum array in the flag-schemas.json entry for the
+batch-update.operations.shortcut (the same enum shown in the diff) to include
"+sheet-show-gridline" and "+sheet-hide-gridline" so schema-validated
+batch-update payloads accept those batchable operations.
🤖 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/sheets/lark_sheet_table_io_test.go`:
- Around line 176-181: The test currently checks error messages with
strings.Contains which is fragile; update the t.Run cases (those calling
parseTablePutPayload with stubFlagView{"sheets": tt.json}) to assert structured
error metadata using errs.ProblemOf(err) — verify the Problem.Category,
Problem.Subtype and any Problem.Param match the expected values from the test
case — and also assert cause preservation by unwrapping the returned error
(errors.Unwrap or errors.Is) to compare against the expected cause value in the
table; replace the strings.Contains assertions with these typed checks to cover
category/subtype/param and preserved cause for parseTablePutPayload and the
other indicated test blocks.

In `@shortcuts/sheets/lark_sheet_table_io.go`:
- Around line 399-401: The code is re-wrapping lower-layer errors with
fmt.Errorf which strips typed errs.* classifications; instead, pass through the
original error from lastDataRow (return nil, err) or, if you must add context at
the command layer, wrap using the appropriate errs.* constructor (e.g., errs.New
or another specific errs.* type) while preserving the original as the cause;
update the occurrences that call lastDataRow and similar calls (the append path
around lastDataRow and the other ranges noted) so they either return the
underlying err directly or wrap it with an errs.* typed error rather than
fmt.Errorf/output.Err*.
- Around line 836-838: The returned tableGetSheet for the "--sheet-id" branch
only sets id and drops the existing sheet name, causing downstream output to
emit name:"" and breaking +table-get -> +table-put round trips; update the
branch that returns []tableGetSheet{{...}} to include the original name (e.g.,
[]tableGetSheet{{id: id, name: t.name}}) and also ensure the emitter that writes
name (the code around t.name at the later emission) uses the preserved name
instead of blanking it so both the return path and the emission path keep the
non-empty sheet name.
- Around line 371-380: The function tablePutFullRange can produce invalid ranges
when totalRows is 0 (e.g., "A1:C0"); add an early guard at the top of
tablePutFullRange to return an empty/no-op range string (""), e.g. if totalRows
<= 0 { return "" }, and apply the same fix to the analogous function around the
730-733 block (the tablePutRange implementation) so both return "" when there
are no rows to write; update any callers/tests expecting a no-op behavior if
needed.

---

Outside diff comments:
In `@shortcuts/sheets/data/flag-schemas.json`:
- Around line 17-68: The enum for batch-update operation shortcuts is missing
the new gridline actions; update the enum array in the flag-schemas.json entry
for the +batch-update.operations.shortcut (the same enum shown in the diff) to
include "+sheet-show-gridline" and "+sheet-hide-gridline" so schema-validated
+batch-update payloads accept those batchable operations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 059d6655-5406-414b-b220-3b8d15bc432d

📥 Commits

Reviewing files that changed from the base of the PR and between 2b4c634 and 82a9838.

📒 Files selected for processing (31)
  • shortcuts/drive/drive_export.go
  • shortcuts/drive/drive_export_test.go
  • shortcuts/drive/drive_import.go
  • shortcuts/sheets/batch_op_contract_test.go
  • shortcuts/sheets/batch_op_dispatch.go
  • shortcuts/sheets/data/flag-defs.json
  • shortcuts/sheets/data/flag-schemas.json
  • shortcuts/sheets/flag_defs_gen.go
  • shortcuts/sheets/flag_schemas_gen.go
  • shortcuts/sheets/lark_sheet_read_data.go
  • shortcuts/sheets/lark_sheet_read_data_test.go
  • shortcuts/sheets/lark_sheet_table_io.go
  • shortcuts/sheets/lark_sheet_table_io_test.go
  • shortcuts/sheets/lark_sheet_workbook.go
  • shortcuts/sheets/lark_sheet_workbook_export_test.go
  • shortcuts/sheets/lark_sheet_workbook_import_test.go
  • shortcuts/sheets/lark_sheet_workbook_test.go
  • shortcuts/sheets/lark_sheet_write_cells.go
  • shortcuts/sheets/shortcuts.go
  • skills/lark-sheets/SKILL.md
  • skills/lark-sheets/references/lark-sheets-chart.md
  • skills/lark-sheets/references/lark-sheets-core-operations.md
  • skills/lark-sheets/references/lark-sheets-filter-view.md
  • skills/lark-sheets/references/lark-sheets-filter.md
  • skills/lark-sheets/references/lark-sheets-financial-modeling-standards.md
  • skills/lark-sheets/references/lark-sheets-range-operations.md
  • skills/lark-sheets/references/lark-sheets-read-data.md
  • skills/lark-sheets/references/lark-sheets-visual-standards.md
  • skills/lark-sheets/references/lark-sheets-workbook.md
  • skills/lark-sheets/references/lark-sheets-write-cells.md
  • tests/cli_e2e/sheets/sheets_crud_workflow_test.go
💤 Files with no reviewable changes (1)
  • shortcuts/sheets/lark_sheet_read_data_test.go

Comment thread shortcuts/sheets/lark_sheet_table_io_test.go Outdated
Comment thread shortcuts/sheets/lark_sheet_table_io.go
Comment thread shortcuts/sheets/lark_sheet_table_io.go Outdated
Comment thread shortcuts/sheets/lark_sheet_table_io.go Outdated
xiongyuanwen-byted and others added 2 commits June 9, 2026 19:52
Add `counta` (count non-empty cells, incl. text) to manage_chart_object
dim2.series[].aggregateType in the chart flag schema. `count` only counts
numeric cells, so counting occurrences of a text/category column renders an
empty chart; `counta` enables category frequency counts. Synced from the
sheet-skill-spec canonical schema.
…y dropping them

A +workbook-create call carrying only cell_merges / row_sizes / col_sizes
(no --values / --sheets and no cell_styles) used to create the workbook
but silently drop the requested visual ops. Two reasons, both fixed:

- workbookCreateStyleDimensions only counted cell_styles when computing
  the write extent, so cell_merges / row_sizes / col_sizes always
  contributed 0 → buildValuesPayload returned a nil payload → Execute
  skipped writeTypedSheets entirely → no visual ops ran. Extend the
  helper to fold the merge / resize ranges in.

- Pure row_sizes / col_sizes payloads can never expand a cell rectangle
  (they are dimension ranges, not cell ranges), so even with the extent
  fix Execute would still skip the write path. Add a no-data branch:
  when payload == nil but a styles item is present, look up the default
  sheet and apply visual ops directly via applyWorkbookCreateVisualOps.
  The dry-run plan mirrors this so the preview shows the visual ops.

Also picks up the --values trailing-JSON-data EOF check (mirror of the
--sheets one in lark_sheet_table_io.go).

End-to-end verified against a real spreadsheet: a cell_merges-only
+workbook-create now produces a sheet with merged_cells_count: 1.
…lidation errors

common.ValidationErrorf goes through fmt.Sprintf, which does not support
%w — the seven call sites that used `%w` were rendering the cause as
literal `%!w(*fmt.wrapError=&{...})` and dropping the cause from the
typed-error chain (so callers couldn't errors.As back to the underlying
error).

Switch each to `%v` for clean rendering and attach the cause via
.WithCause(err) so the typed contract is preserved. Touched call sites:

- lark_sheet_dataframe.go: --dataframe Arrow decode / stdin read / file
  read failures (3 call sites).
- lark_sheet_table_io.go: --sheets invalid JSON, payload-validate
  per-cell coercion error, buildSheetMatrix per-cell error,
  --dataframe-out arrow encode failure (4 call sites).

End-to-end verified against a real spreadsheet: both invalid-JSON and
typed-cell errors now render readable messages instead of %!w(...).
…hema

Mirror of the sheet-skill-spec change adding the two gridline shortcuts
to cli-schemas.json batch_update.operations.shortcut enum. Synced from
the upstream canonical via generate:cli + sync:cli.

Verified end-to-end on a real spreadsheet — +batch-update with a
+sheet-hide-gridline op passes schema validation and the backend run
returns succeeded: 1.
Mirror of the sheet-skill-spec update that documents +workbook-export's
default-no-download behavior and its relationship to drive +export
--doc-type sheet. Synced from canonical via generate:cli + sync:cli +
go generate.

End-to-end verified against a real spreadsheet:
- Omit --output-path → ok:true, downloaded:false, file_token returned
- Pass --output-path ./crfix_test.xlsx → ok:true, file saved
  (17892 bytes), saved_path returned

The --help output for +workbook-export now states the default behavior
and points callers at `drive +export --doc-type sheet` when they need
the --output-dir / --file-name / --overwrite split.
…ings

Per the coding guideline "Error-path tests must assert typed metadata via
errs.ProblemOf (category / subtype / param) and cause preservation, not
message substrings alone." — sweep through every error-path assertion in
the sheets domain and replace the
`strings.Contains(stdout+stderr+err.Error(), ...)` pattern with two
small helpers landed in helpers_test.go:

  requireProblem(t, err, wantCategory, wantSubtype, msgContains)
    -> *errs.Problem
  requireValidation(t, err, msgContains)
    -> *errs.ValidationError   // shorthand for CategoryValidation +
                               //   SubtypeInvalidArgument; lets callers
                               //   also assert .Param / .Params / .Cause

~60 assertion sites across 18 test files now check the typed envelope
shape, with message-substring checks moved onto the returned Problem
(.Message / .Hint / .Param). The substring is preserved as a sanity
check rather than the sole assertion, so a category drift like
validation → internal would now fail loudly instead of slipping past.

Cases intentionally left as substring (each with a one-line reason):
  - Errors that come straight from cobra's native flag parser (untyped
    *errors.errorString — e.g. "required flag(s) ... not set", mutually-
    exclusive groups). Re-typing these needs a custom FlagErrorFunc and
    is out of scope here.
  - Intermediate errors from decodeArrowToSheet that the caller wraps
    into a typed envelope (`//nolint:forbidigo` reason). Those unit
    tests assert the unwrapped intermediate directly.

One production tweak:
  - shortcuts/sheets/flag_schema.go: printFlagSchemaFor returns typed
    *errs.ValidationError (with WithParam("--flag-name") on the
    unknown-flag branch) instead of raw fmt.Errorf. The framework
    already wraps this when called via --print-schema, so user-facing
    behaviour is unchanged; direct callers (and tests) now get the
    typed envelope.

Verified: go test ./shortcuts/sheets/... passes; golangci-lint
--new-from-rev=origin/main reports 0 issues.
…ings

Mirror of the sweep just landed in shortcuts/sheets: replace error-path
substring assertions with typed-envelope checks via two small helpers
landed in a new shortcuts/common/typed_error_assertions_test.go:

  requireProblem(t, err, wantCategory, wantSubtype, msgContains)
    -> *errs.Problem
  requireValidation(t, err, msgContains)
    -> *errs.ValidationError   // shorthand for CategoryValidation +
                               //   SubtypeInvalidArgument; lets callers
                               //   also assert .Param / .Params / .Cause

8 sites moved to typed assertions across runner_jq_test.go,
mcp_client_test.go, drive_media_upload_typed_test.go, and
runner_input_test.go (the input tests already used a typed-param helper;
this just retargets the substring follow-up onto the typed Message).

Sites intentionally left as substring + comment (production returns raw
fmt.Errorf, not a typed envelope):
  - runner_botinfo_test.go (6 sites): BotInfo / fetchBotInfo wrap upstream
    errors with fmt.Errorf so the SDK-level message ([99991], 403,
    invalid character, etc.) shows through.
  - runner_args_test.go (4 sites in 2 tests): rejectPositionalArgs returns
    raw fmt.Errorf to satisfy cobra's PositionalArgs contract.
  - permission_grant_test.go (2 sites): assert on stderr / hint strings,
    not error messages — already out of the err.Error() substring class.

No production code changes.

Verified: go test ./shortcuts/common/... passes;
golangci-lint --new-from-rev=origin/main ./shortcuts/common/... reports
0 issues.
@xiongyuanwen-byted

Copy link
Copy Markdown
Collaborator Author

Code review round-up

All 10 inline review comments now have point-by-point replies on the diff. Summarizing here for the two CodeRabbit "outside diff range" notes and the overall picture:

Outside-diff CodeRabbit notes

  • data/flag-schemas.json+batch-update.operations.shortcut enum missing gridline shortcuts → fixed in e9c4b1e (CLI) + upstream sheet-skill-spec 7584e57. Verified end-to-end: +batch-update --operations '[{"shortcut":"+sheet-hide-gridline","input":{"sheet_id":"…"}}]' now passes schema validation and the backend returns succeeded: 1, failed: 0.
  • lark_sheet_workbook_test.go:431-450 — typed metadata assertions → covered by the domain-wide sweep in b276e92. The workbook test now uses requireProblem(t, err, errs.CategoryConfirmation, errs.SubtypeConfirmationRequired, "") for the high-risk confirmation gate, and requireValidation for the other negative-path cases.

Sites intentionally left as substring (each carries a one-line comment in code)

  • lark_sheet_dataframe_test.go ×3 — decodeArrowToSheet returns intermediate fmt.Errorf per its //nolint:forbidigo reason "command layer wraps it"; the unit tests assert the unwrapped intermediate directly.
  • lark_sheet_search_replace_test.go ×1 — cobra's native ValidateRequiredFlags fires before our Validate hook and produces a plain *errors.errorString. Re-typing this needs a custom FlagErrorFunc (sheets does not install one today; only mail does), which is out of scope here.
  • lark_sheet_workbook_test.go ×2 — same cobra-native flag-parser errors (mutually-exclusive group + missing required flag).
  • shortcuts/common/runner_botinfo_test.go ×6 — BotInfo / fetchBotInfo wrap upstream SDK errors with raw fmt.Errorf (the SDK-level message [99991] / 403 / invalid character shows through). Touching that is a common-infra change unrelated to this PR.
  • shortcuts/common/runner_args_test.go ×4 — rejectPositionalArgs returns raw fmt.Errorf to satisfy cobra's PositionalArgs contract.

Total: ~70 substring sites moved to typed envelope assertions across 25 test files; the ~16 left are all listed above with a stated reason and a code comment.

Commits answering the inline comments

Topic Commit
Drop manual capacity grow / data-safety hole d2517180
+workbook-create style-only ops no longer dropped 1aa3305f
+table-get duplicate header rejected with --no-header hint 086876d2
--dataframe stdin guard shared with common Input resolver d994c278
--sheets / --values JSON trailing data rejected 086876d2
--sheet-id fallback uses id as name to keep round-trip intact 086876d2
tablePutFullRange empty-matrix dry-run 086876d2
ValidationErrorf %w%v + .WithCause(err) (×7) 9ef0d370
+batch-update enum gridline + upstream spec sync e9c4b1e1
Typed-Problem assertions sweep (sheets) b276e92f
Typed-Problem assertions sweep (common) 47a3c1c6
+workbook-export doc + drive +export cross-reference a07b178b

Every behavior fix above was end-to-end verified against a real spreadsheet (PPE) — not just unit tests. The cases live in the per-comment replies.

…ed in CR

Four review-flagged bugs, all in lark_sheet_table_io.go (bundled because
they touch the same file and the same +table-put / +table-get domain):

1. +table-get --dry-run dropped the --sheet-id / --sheet-name selector
   from the get_cell_ranges body, while Execute always passed it. Agents
   that validate the dry-run shape and then run live would see a request
   shape mismatch. The dry-run now calls sheetSelectorForToolInput so
   the body matches Execute.

2. isDateNumberFormat used a simple `strings.ContainsRune(_, 'y')` so
   number formats like "JPY #,##0" (a currency prefix that happens to
   contain a lone 'Y') were misread as date formats — round-tripping
   integer cells out as ISO dates. The detector is now token-aware:
   it skips quoted "...", `\\x`-escaped, and `[...]` bracket sections,
   and only fires on an unescaped `yy` (a real Excel year token).

3. sheetCreateDims sized new append-mode sheets by `headerOn(s)` only,
   but writeSheetData forces a header on empty append sheets when
   Header == nil. Near 50000 rows / 200 cols this created the sheet one
   row short and the follow-up set_cell_range bounced off the backend
   ceiling. Size now matches the forced-header logic exactly.

4. tableGetTargets fallback paths (read-failure / selector mismatch on
   --sheet-id) returned a target with name="" — already corrected for
   --sheet-id structure-success path in 086876d, but the structure-
   failure fallback still left it empty. Use the id as the name there
   too so the +table-get → +table-put round-trip never breaks on a
   nameless sheet.

End-to-end verified against a real spreadsheet:
- table-get --dry-run with --sheet-name / --sheet-id both render the
  selector field in the get_cell_ranges body
- A real round-trip (typed put → get) preserves dtypes + formats
readDataframeBytes used to read the whole Arrow file unbounded — a
stdin / file > 1 GiB would OOM the CLI long before the backend
per-sheet ceilings kicked in. decodeArrowToSheet then materialized
every record into [][]interface{} regardless of size.

Three caps now match the backend's per-sheet hard ceilings:
- byte cap: 256 MiB (covers worst-case 200×50000 cells × ~25 B Arrow
  overhead). File path pre-Stat()s before opening; both file and stdin
  paths read through io.LimitReader so an oversized input is rejected
  without allocating the full payload.
- column cap: 200, checked at schema-decode time before allocating any
  per-column slices.
- row cap: 50000, checked during record-batch iteration so a 1M-row
  Arrow file is rejected mid-stream instead of fully decoding first.

End-to-end verified against PPE — a 257 MiB file is rejected at file-
Stat with a typed validation error before any read happens.
…orkError

The poll loop in RunExport returned ctx.Err() directly in two places —
on the inter-attempt sleep cancel and on the pre-attempt deadline check.
That let context.Canceled / context.DeadlineExceeded escape as untyped
errors at the cobra layer, bypassing the typed-error contract every
other failure path already honors.

Add wrapExportContextErr that maps both into errs.NewNetworkError with
SubtypeNetworkTransport / SubtypeNetworkTimeout respectively and
preserves the cause via .WithCause(err), so callers can still
errors.Is(err, context.Canceled) downstream.

CR-flagged at drive_export.go:229 / :234.
The dependency-license check still has to --ignore Apache Arrow wholesale
because go-licenses' classifier parses its LICENSE.txt as a single license
and mis-reports the module as LicenseRef-C-Ares / Unknown (Arrow inlines
the c-ares 3rdparty notice alongside its own Apache-2.0). Re-classifying
on our side isn't possible without changing go-licenses itself.

The CR concern was that --ignore is too wide — a future Arrow re-license
or new inlined dep would silently sail through. Add a follow-up step that
re-checks Arrow's LICENSE.txt independently: it must still open with
"Apache License" AND must still inline the c-ares 3rdparty notice (the
two facts that make the --ignore safe today). If either invariant breaks,
CI fails here and forces a human to re-evaluate the ignore.

Verified locally — both assertions pass against the current pinned
Arrow v17.
Mirror of the sheet-skill-spec change that fixes three places teaching
an invalid +table-put payload shape — the typed protocol only has
columns / data / dtypes / formats (no formula field) and must always
be wrapped in an outer {"sheets":[...]} envelope. write-cells and the
SKILL.md decision table previously used the wrong field names (type /
format) and pointed users at +table-put for formula writes, which the
shortcut can't actually accept.

Synced from upstream canonical via generate:cli + sync:cli.
…-create

AGENTS.md requires a dry-run E2E for every new shortcut and a live E2E
for new flows. Three new files cover the four shortcuts this branch
adds or materially changes:

- sheets_gridline_dryrun_test.go — pins +sheet-show-gridline /
  +sheet-hide-gridline as a single modify_workbook_structure call with
  the right operation name (show_gridline / hide_gridline) and
  sheet_id, so an op-name typo would trip CI before any live run.

- sheets_workbook_import_dryrun_test.go — pins +workbook-import as a
  two-step plan (drive media upload + drive import-task create) with
  the doc type hard-coded to "sheet" — the wrapper's whole reason for
  existing on top of generic drive +import. --name reaches file_name
  on the wire; file_extension is sniffed from the local file.

- sheets_table_put_typed_workflow_test.go — two live workflows running
  against a freshly created spreadsheet. The first runs the full
  typed +table-put → +table-get round-trip (date / numeric / object
  columns with custom number_format) and asserts the dtype + format
  contract holds end-to-end. The second exercises the typed
  +workbook-create --sheets path: create + write in one shortcut, the
  payload sheet name adopts the workbook's default sheet (no empty
  "Sheet1" left behind), and the typed contract still survives the
  read-back.

End-to-end verified locally (user identity): typed put round-trips
preserve dtypes (date → datetime64[ns], numeric → float64, object →
object) + formats verbatim; workbook-create adopts the named sheet as
the first sheet with the same typed shape intact.
…spec

Mirror of the sheet-skill-spec change that adds a DataFrame ↔ JSON
bridge as a skill-bundled Python script instead of inside the CLI
binary. Per PR #1355 review (docx NcmxdRo2yoZ4OXxoMUZcxRZ7nHd, §4.2):
keep the CLI a thin JSON/REST client; pandas / Arrow editing lives in
the caller's Python process. Synced from canonical via generate:cli +
sync:cli.

- skills/lark-sheets/scripts/sheets_df.py (new): pandas DataFrame ↔
  one sheet, .parquet / .feather / .arrow / .csv / .json. Shells out to
  `+table-put` / `+table-get` over typed JSON — no CLI changes.
- SKILL.md decision tree + write-cells.md +table-put section: explicit
  pointers so pandas users land on the script instead of hand-rolling
  the `--sheets` payload.

End-to-end verified against PPE: 3-row DataFrame (datetime / float /
object) round-trips parquet → script put → real sheet → script get →
parquet with dtypes preserved.
Mirror of the sheet-skill-spec change that ships a 32-line helper-only
sheets_df.py (df_to_sheet + sheet_to_df) and removes the corresponding
inline `def` blocks from three reference docs.

- skills/lark-sheets/scripts/sheets_df.py (new): pandas DataFrame ↔
  one +table-put / +table-get sheet, importable as a library. Same
  helper pair the docs already taught, lifted out of the prose so
  callers can `from sheets_df import df_to_sheet, sheet_to_df`.
- lark-sheets-write-cells.md / lark-sheets-read-data.md /
  lark-sheets-workbook.md: drop the inline helper definitions; keep
  the usage examples (single/multi-sheet, round-trip) and switch them
  to import-from-script. workbook reference's +workbook-create
  --sheets section now points pandas users at the helper directly
  (was previously a textual reference back to write-cells).

End-to-end verified against PPE (--as user):
- +workbook-create with df_to_sheet for three sheets (income / balance
  / cashflow): create ok, dtypes (datetime64[ns] / float64) + formats
  (#,##0 / 0.0% / yyyy-mm-dd) survive on read-back through sheet_to_df.
- read → pandas mutate → write-back round-trip preserves both data
  and formats.
The previous commit (5fac9c3) shipped sheets_df.py and inadvertently
included its `__pycache__/sheets_df.cpython-312.pyc` — local Python
import created the bytecode cache during PPE round-trip verification and
`git add skills/lark-sheets/` swept it in.

Remove the pyc and add Python bytecode patterns to .gitignore so the
skill-bundled helper scripts don't pull cache files into future commits.
Per the design review at NcmxdRo2yoZ4OXxoMUZcxRZ7nHd, the Arrow IPC binary
input/output channel adds a heavy columnar runtime to the CLI for no new
capability — the typed JSON --sheets path already covers everything, and
the column-major / zero-copy advantages collapse the moment the CLI re-
encodes into the row-oriented sheets OpenAPI JSON body. Removing it also
lets us drop the `--ignore github.com/apache/arrow/go/v17` license-check
escape hatch.

Deleted:
- shortcuts/sheets/lark_sheet_dataframe.go (+ test)
- --dataframe branches in +table-put / +workbook-create
- --dataframe-out branch in +table-get
- StdinConsumed / MarkStdinConsumed exported methods (the binary stdin
  reader was the only out-of-band consumer); internal stdinConsumed
  guard against duplicate `-` input flags stays
- apache/arrow/go/v17 + transitive deps via `go mod tidy`
- CI go-licenses --ignore for arrow and the LICENSE.txt assertion step
- --dataframe / --dataframe-out coverage in skill references

Pandas users keep the round-trip via the existing skill script
skills/lark-sheets/scripts/sheets_df.py over the JSON path.

The full pre-removal state is preserved on branch feat/sheets-arrow-stash.

Upstream sheet-skill-spec follow-up: the two flag rows in the canonical
spec + base table tblV2F6fqIjyCFQW must also be dropped so the next sync
does not re-add them.
Mirrors sheet-skill-spec 5562f83. The +table-put / +workbook-create
--sheets flag descriptions (and the --print-schema description on the
sheets array) now point at the existing df_to_sheet helper instead of
the previous misleading one-liner that produced a dict missing the
outer {"sheets":[...]} envelope and the per-sheet `name`. Agents that
copy-paste the description verbatim now build a valid payload.

Auto-synced via spec's generate:cli + sync:consumers; go generate
./shortcuts/sheets/... regenerated flag_defs_gen.go so its embedded
flagDefs stays byte-equal to data/flag-defs.json.
AGENTS.md requires both dry-run and live E2E for every newly registered
shortcut, and behavior-changing refactors need at least the matching
half. Three gaps remained on feat/lark-sheets-develop:

- +sheet-show-gridline / +sheet-hide-gridline (new): only dry-run E2E.
  Add sheets_gridline_workflow_test.go — create a real spreadsheet,
  toggle hide then show against a live sub-sheet, assert ok=true on
  both (gridline state is write-only — there is no read-back field on
  +sheet-info / +workbook-info — so a successful envelope is the
  meaningful signal; the dry-run E2E already pins the wire shape).

- +workbook-import (new): only dry-run E2E. Add
  sheets_workbook_import_workflow_test.go — write a local CSV, run
  the full upload → create-task → poll, assert ready=true with a
  sheet token, +info confirms the imported workbook is reachable,
  cleanup deletes the spreadsheet.

- +workbook-export refactor (no-download default changed): had live
  E2E but no dry-run E2E in tests/cli_e2e/. Add
  sheets_workbook_export_dryrun_test.go — pin the three sheet-
  specific differences vs drive +export: type=sheet hard-coded,
  csv mode routes --sheet-id onto sub_id (xlsx mode omits it), and
  --output-path maps onto the dry-run plan's top-level output_dir.
  Also pins the csv-without-sheet-id validation error.
…ailure

Three "made it halfway and stopped" exits in the sheets domain previously
disagreed on shape, which made the post-failure recovery flow hard for
agents to predict from one command to another:

- +table-put partial write           → exit 1, stdout ok:false envelope
- +table-put zero-sheet write        → exit 1, stderr api/server_error
- +workbook-create create-but-fill   → exit 2, stderr validation/failed_precondition

OutPartialFailure exists exactly for "the side effect landed but the
follow-up didn't" — it stamps an ok:false result envelope on stdout
(carrying the state the caller needs to recover) and returns the bare
partial-failure exit signal. The workbook-create fill-failure path was
the odd one out: it surfaced as a typed failed_precondition error on
stderr, which agents couldn't tell apart from a plain validation refusal
even though the spreadsheet really did exist and a retry / cleanup was
possible.

Migrate workbookCreatedButFillFailed onto OutPartialFailure so the four
call sites in +workbook-create's Execute (sheet-resolve failure, initial
fill failure, style-only resolve failure, style-only apply failure) emit
the same envelope shape +table-put's partial write does:

  {
    "ok": false,
    "data": {
      "spreadsheet_token": "shtNEW",
      "reason": "spreadsheet shtNEW created but initial fill failed",
      "hint":   "the spreadsheet exists; retry the fill … or delete it",
      "cause":  {"category": "...", "subtype": "...", "message": "..."}
    }
  }

The inner failure's typed problem (category / subtype / message) is
flattened into the `cause` field so agents stay diagnosable from the JSON
envelope alone, instead of having to errors.Unwrap a Go error.

Updated TestExecute_WorkbookCreate_FillFailureKeepsToken to assert the
new shape (ok:false envelope on stdout, *output.PartialFailureError exit
signal, structured cause carrying the underlying invalid_response
subtype) — preserving the original test intent (token must survive for
recovery; inner cause must stay diagnosable) under the new contract.
- shortcuts/sheets/flag_schema_validate.go:106 — composite-JSON shape
  validation was wrapping vErr's message into a typed sheets validation
  error without preserving vErr as the typed cause; add the missing
  .WithCause(vErr) so errors.Unwrap and ProblemOf still find the
  underlying validator error (matches every other typed-error chain
  helper in the file).

- shortcuts/sheets/lark_sheet_batch_update.go:92 — comment claimed
  batchUpdateInput returns "FlagErrorf-typed errors", but FlagErrorf no
  longer exists (the typed-error migration replaced it with
  common.ValidationErrorf / errs.ValidationError); update the comment
  to reflect what is actually returned.

- shortcuts/drive/drive_export.go:121 — drop the ValidateExport public
  alias and rename to validateExport. sheets +workbook-export reuses
  RunExport / PlanExportDryRun from this package but inlines its own
  (sheet-specific) Validate, so there is no cross-package call site —
  ValidateExport was a misleading sibling of the genuinely-shared
  ValidateImport. Comment added to record the asymmetry so future
  readers do not export it back.
The earlier --dataframe / --dataframe-out + apache/arrow/go/v17 removal
deleted the arrow consumer but left two indirect lines in go.mod pinned
to the versions arrow had pulled in:

  - github.com/kr/text                   v0.2.0
  - golang.org/x/exp  v0.0.0-20240222234643-814bf88cf225

With arrow gone, larksuite/cli was the only requirer of those exact
versions; every real consumer needs lower ones (kr/pretty wants
kr/text v0.1.0; charmbracelet/huh wants x/exp …20231006; xo/terminfo
wants x/exp …20220909). Removing the two indirect lines and running
`go mod tidy` lets MVS pick the real-consumer versions and drops the
explicit indirect entries entirely — go.mod net-diff against main is
now zero for this branch.

Verified locally: go build ./...; go test ./shortcuts/sheets/...
./shortcuts/drive/... ./shortcuts/common/... ./internal/auth/...
./cmd/auth/... — all green.
@xiongyuanwen-byted xiongyuanwen-byted merged commit bd898a1 into main Jun 25, 2026
23 checks passed
@xiongyuanwen-byted xiongyuanwen-byted deleted the feat/lark-sheets-develop branch June 25, 2026 02:48
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.

6 participants