From 4f4c0b59c99a77393cd810c516eba794f2405d5e Mon Sep 17 00:00:00 2001 From: fangshuyu-768 Date: Tue, 9 Jun 2026 18:11:56 +0800 Subject: [PATCH 1/2] docs(lark-doc): replace append with block_insert_after in skeleton workflow guidance (#1340) `append` always inserts at document end (equiv. `block_insert_after --block-id -1`), but skill docs previously recommended it for the "skeleton + chapter-by-chapter fill" pattern, causing all content to pile up after the last heading. Changes: - Remove `append` from skeleton workflow guidance in `lark-doc-create-workflow.md` and `lark-doc-create.md`; recommend `block_insert_after` with explicit `--block-id` - Fix `block_move_after` required params: remove `--content` (not supported), only `--block-id` and `--src-block-ids` are valid - Add bash language tag to code block for proper highlighting --- skills/lark-doc/references/lark-doc-create.md | 2 +- skills/lark-doc/references/lark-doc-update.md | 4 ++-- skills/lark-doc/references/style/lark-doc-create-workflow.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/skills/lark-doc/references/lark-doc-create.md b/skills/lark-doc/references/lark-doc-create.md index 5d7fa5f02..27c36f1cc 100644 --- a/skills/lark-doc/references/lark-doc-create.md +++ b/skills/lark-doc/references/lark-doc-create.md @@ -73,7 +73,7 @@ lark-cli docs +create --api-version v2 --doc-format markdown --content $'# 项 ## 最佳实践 - 文档标题从内容中自动提取(XML `` 或 Markdown `#`),不要在内容开头重复写标题 -- **创建较长的文档时只建骨架**:`--content` 仅传标题 + 各级 heading + 简短占位摘要;正文留给后续 `docs +update --command append` 或 `block_insert_after` 分段追加。一次性塞超长 `--content` 既容易触发参数限制,调试也更难。 +- **创建较长的文档时只建骨架**:`--content` 仅传标题 + 各级 heading + 简短占位摘要;正文留给后续 `block_insert_after --block-id <章节标题 block_id>` 分段追加。一次性塞超长 `--content` 既容易触发参数限制,调试也更难。 - **视觉丰富度**:必须遵循 [`lark-doc-style.md`](style/lark-doc-style.md) 中的样式指南,主动使用结构化 block 丰富文档 ## 参考 diff --git a/skills/lark-doc/references/lark-doc-update.md b/skills/lark-doc/references/lark-doc-update.md index ce1d3d320..c84378e34 100644 --- a/skills/lark-doc/references/lark-doc-update.md +++ b/skills/lark-doc/references/lark-doc-update.md @@ -40,8 +40,8 @@ | `block_replace` | 替换指定 block(同一 block 仅限一次) | `--block-id` `--content` | | `block_delete` | 删除指定 block(逗号分隔可批量) | `--block-id` | | `overwrite` | ⚠️ 清空文档后全文重写(可能丢失图片、评论) | `--content` | -| `append` | 在文档末尾追加内容(等价于 `block_insert_after --block-id -1`) | `--content` | -| `block_move_after` | 移动已有 block 到指定位置 | `--block-id` + (`--content` 或 `--src-block-ids`) | +| `append` | ⚠️ 在文档**末尾**追加内容(等价于 `block_insert_after --block-id -1`)。**不适用于逐章填充**——逐章写入请用 `block_insert_after` 并指定对应标题的 `--block-id` | `--content` | +| `block_move_after` | 移动已有 block 到指定位置 | `--block-id` `--src-block-ids` | ## 指令示例 diff --git a/skills/lark-doc/references/style/lark-doc-create-workflow.md b/skills/lark-doc/references/style/lark-doc-create-workflow.md index 464656714..1800a369b 100644 --- a/skills/lark-doc/references/style/lark-doc-create-workflow.md +++ b/skills/lark-doc/references/style/lark-doc-create-workflow.md @@ -22,14 +22,14 @@ 2. 设计大纲——每个 h1/h2 章节至少规划 1 个非文本 block;承载重要信息的章节优先规划画板 3. `docs +create --api-version v2` **只建骨架**:标题 + 开头 `<callout>` + 各级标题 + 每节一句占位摘要 - ⚠️ **不要**一次性把完整章节内容塞进 `--content`。超长 `--content` 容易触发字符/参数限制。 - - 完整内容留到第二波,由各 Agent 用 `docs +update --command append` 或 `block_insert_after` 分段写入。 + - 完整内容留到第二波,由各 Agent 用 `block_insert_after --block-id <章节标题 block_id>` 分段写入。 ### 第二波 — 内容撰写(并行 Agent) 4. Spawn Agent 并行撰写各章节。每个 Agent 需收到: - 文档 token、负责的章节范围、期望的 block 类型 - `lark-doc-xml.md` 和 `lark-doc-style.md` 的完整路径(Agent 须先读取) - - 使用 `docs +update --command append` 或 `block_insert_after` 写入 + - 使用 `block_insert_after --block-id <章节标题 block_id>` 写入对应章节内容 ### 第三波 — 整合审查 + 画板意图识别(串行) From eed711bb11b4ccbff263fa4d2a08f56a3d7b0a11 Mon Sep 17 00:00:00 2001 From: xiongyuanwen-byted <xiongyuanwen@bytedance.com> Date: Tue, 9 Jun 2026 19:48:28 +0800 Subject: [PATCH 2/2] feat(sheets): guard +csv-put --csv against a path passed without @ (#1337) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit +csv-put --csv data.csv (a forgotten @) was silently written as one-cell content, because any string parses as valid CSV — unlike malformed JSON it never errored, so the filename landed in the sheet instead of the file's contents. +csv-put's Validate now rejects a --csv value when it names a real file in the cwd subtree (guardCSVValueIsNotFilePath; fileIO.Stat, fail-open), hinting to use --csv @file or stdin (--csv -). Scoped to --csv only — no framework or other-flag change. Checking real existence (not name shape) lets inline content that merely ends in a filename pass through. Adds TestGuardCSVValueIsNotFilePath. --- shortcuts/sheets/csv_put_guard_test.go | 58 ++++++++++++++++++++++ shortcuts/sheets/lark_sheet_write_cells.go | 37 +++++++++++++- 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 shortcuts/sheets/csv_put_guard_test.go diff --git a/shortcuts/sheets/csv_put_guard_test.go b/shortcuts/sheets/csv_put_guard_test.go new file mode 100644 index 000000000..5191e45ee --- /dev/null +++ b/shortcuts/sheets/csv_put_guard_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package sheets + +import ( + "os" + "strings" + "testing" + + "github.com/larksuite/cli/internal/cmdutil" + _ "github.com/larksuite/cli/internal/vfs/localfileio" + "github.com/larksuite/cli/shortcuts/common" + "github.com/spf13/cobra" +) + +func newCSVGuardRuntime(csvVal string) *common.RuntimeContext { + cmd := &cobra.Command{Use: "test"} + cmd.Flags().String("csv", "", "") + cmd.ParseFlags(nil) + cmd.Flags().Set("csv", csvVal) + return &common.RuntimeContext{Cmd: cmd} +} + +// TestGuardCSVValueIsNotFilePath verifies the guard flags a bare --csv value +// only when it names a real file (a forgotten @), while leaving genuine inline +// content alone — including the case the old name-shape heuristic got wrong: +// prose that merely ends in or mentions a filename. +func TestGuardCSVValueIsNotFilePath(t *testing.T) { + dir := t.TempDir() + cmdutil.TestChdir(t, dir) + if err := os.WriteFile("data.csv", []byte("a,b\n1,2\n"), 0644); err != nil { + t.Fatal(err) + } + + // Bare value naming an existing file → guarded with a fix-it hint. + err := guardCSVValueIsNotFilePath(newCSVGuardRuntime("data.csv")) + if err == nil { + t.Fatal("expected guard error when --csv names an existing file") + } + if !strings.Contains(err.Error(), "existing file") || !strings.Contains(err.Error(), "@data.csv") { + t.Errorf("error should flag the file and suggest @data.csv, got: %v", err) + } + + // Content that is not a real file must pass through unchanged. + for _, v := range []string{ + "改完记得更新config.json", // prose ending in a filename — not a real file + "remember to update data.csv", // mentions the real file but isn't its name + "a,b\n1,2", // multi-cell CSV + "hello world", + "nope.csv", // path-shaped but no such file + "", + } { + if err := guardCSVValueIsNotFilePath(newCSVGuardRuntime(v)); err != nil { + t.Errorf("content %q must pass through, got: %v", v, err) + } + } +} diff --git a/shortcuts/sheets/lark_sheet_write_cells.go b/shortcuts/sheets/lark_sheet_write_cells.go index 07768614f..9cb51fbae 100644 --- a/shortcuts/sheets/lark_sheet_write_cells.go +++ b/shortcuts/sheets/lark_sheet_write_cells.go @@ -219,7 +219,12 @@ var CsvPut = common.Shortcut{ } cmd.MarkFlagsOneRequired("start-cell", "range") }, - Validate: validateViaInput(csvPutInput), + Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { + if err := guardCSVValueIsNotFilePath(runtime); err != nil { + return err + } + return validateViaInput(csvPutInput)(ctx, runtime) + }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) @@ -295,6 +300,36 @@ func csvPutWriteRangeFromInput(input map[string]interface{}) (string, bool) { return fmt.Sprintf("%s:%s%d", anchor, endCol, endRow), true } +// guardCSVValueIsNotFilePath catches the common slip of passing a CSV file path +// to --csv without the "@" that reads it (e.g. `--csv data.csv` instead of +// `--csv @data.csv`). Because any string is a valid one-cell CSV, the mistake +// would otherwise be written silently as the literal text "data.csv". It runs +// in +csv-put's Validate, after resolveInputFlags — so an @file / stdin value is +// already its contents (a real CSV blob, never a path) and only a bare value +// reaches here unchanged. It flags the value only when it actually names an +// existing file in the cwd subtree; checking real existence (not name shape) +// means inline content that merely ends in a filename ("see config.json") is +// never misjudged. Fails open: any Stat error or a directory leaves the value +// untouched. Scoped to --csv only — no other flag is affected. +func guardCSVValueIsNotFilePath(runtime *common.RuntimeContext) error { + raw := strings.TrimSpace(runtime.Str("csv")) + if raw == "" { + return nil + } + fio := runtime.FileIO() + if fio == nil { + return nil + } + info, err := fio.Stat(raw) + if err != nil || info == nil || info.IsDir() { + return nil //nolint:nilerr // fail-open: a missing/unreadable path is treated as inline content, not a forgotten @ + } + return common.FlagErrorf( + "--csv value %q is an existing file, not inline CSV; to read it use --csv @%s, or pass the literal text via stdin (--csv -)", + raw, raw, + ) +} + func csvPutInput(runtime flagView, token, sheetID, sheetName string) (map[string]interface{}, error) { if err := requireSheetSelector(sheetID, sheetName); err != nil { return nil, err