Skip to content

feat(drive): add +status shortcut for content-hash diff#692

Open
fangshuyu-768 wants to merge 5 commits intomainfrom
feat/drive-status
Open

feat(drive): add +status shortcut for content-hash diff#692
fangshuyu-768 wants to merge 5 commits intomainfrom
feat/drive-status

Conversation

@fangshuyu-768
Copy link
Copy Markdown
Collaborator

@fangshuyu-768 fangshuyu-768 commented Apr 28, 2026

Summary

Adds a new drive +status shortcut: a read-only diff primitive that walks --local-dir, recursively lists --folder-token, and reports four buckets by SHA-256 content hash.

  • new_local — only in --local-dir
  • new_remote — only under --folder-token
  • modified — both sides exist, hashes differ
  • unchanged — both sides exist, hashes match

Designed as the foundation any future +push / +pull / +sync workflow can build on.

Design notes

  • SHA-256 streamed in memory. Drive's list/metas APIs do not expose a content hash, so files present on both sides are downloaded via GET /open-apis/drive/v1/files/:file_token/download and hashed via io.Copy(sha256.New(), resp.Body) — never written to disk. Files only on one side are not fetched. Command stays Risk: "read".
  • type=file only. Online docs (docx / sheet / bitable / mindnote / slides) and shortcuts are skipped because there is no equivalent local binary to hash against. Subfolders recurse via the standard list pagination loop.
  • --local-dir is bounded by cwd through three layers: (1) the framework's validate.SafeLocalFlagPath("--local-dir", ...) runs first so the error message references the correct flag name; (2) runtime.FileIO().Stat re-applies SafeInputPath for existence + IsDir checks; (3) per-file runtime.FileIO().Open during walk. Absolute paths and any .. that escapes cwd are rejected with structured validation errors.
  • filepath.WalkDir is annotated with //nolint:forbidigo. Shortcuts can't import internal/vfs and the runtime FileIO interface has no walker today; the bare walk is fine here because SafeInputPath has already bounded the root. Worth replacing once a runtime-level walker lands.
  • Scopes: drive:drive.metadata:readonly (list folders) + drive:file:download (fetch files for hashing). The broader drive:drive is disabled by enterprise policy in some tenants; the narrower pair was verified end-to-end.

Output shape

{
  "new_local":  [{"rel_path": "..."}],
  "new_remote": [{"rel_path": "...", "file_token": "..."}],
  "modified":   [{"rel_path": "...", "file_token": "..."}],
  "unchanged":  [{"rel_path": "...", "file_token": "..."}]
}

Test plan

A. Static checks

# Check Result
A1 go build ./... ✅ no output
A2 go vet ./... ✅ no output
A3 golangci-lint run --new-from-rev=origin/main ./shortcuts/drive/... ✅ 0 issues (only nolint:forbidigo on the walker, with comment)
A4 node scripts/skill-format-check/index.js skills ✅ Skill format check passed
A5 Full non-e2e suite: `go test $(go list ./... grep -v cli_e2e) -count=1`
A6 CI (latest force-push) ✅ 21/21 checks green

B. Unit tests (go test ./shortcuts/drive/... -run TestDriveStatus)

# Test Coverage
B1 TestDriveStatusCategorizesByHash All 4 buckets in one run with a nested subfolder; verifies docx and shortcut entries are skipped
B2 TestDriveStatusRejectsMissingLocalDir --local-dir points at a path that doesn't exist → validation error
B3 TestDriveStatusRejectsLocalFile --local-dir points at a regular file → "is not a directory" error
B4 TestDriveStatusRejectsAbsoluteLocalDir --local-dir /etc → SafeInputPath rejection
B5 TestShortcutsIncludesExpectedCommands Registry includes +status

C. End-to-end against a real Drive folder (happy path)

Local tree (10 files, 7 directories) ↔ remote tree (9 files, 4 directories):

Bucket Count Items
modified 3 conflict.txt, docs/changelog.md, docs/中文文档.md
unchanged 3 docs/readme.md, images/logo.bin (binary), stub.txt (1 byte)
new_local 4 deeply/nested/path/deep.txt (4-level nesting), empty-local-only.txt (0 bytes), images/new-icon.bin, shared/notes.txt
new_remote 3 archive/2025-summary.md (remote-only subdir), docs/deprecated.md, images/old-icon.bin
  • ✅ Total 13 items classified, no double-bucketing
  • ✅ Each bucket sorted by rel_path (lexicographic)
  • ✅ Multilingual paths (docs/中文文档.md) round-trip without mojibake
  • ✅ Binary content (PNG header + payload in images/logo.bin) hashes identically on both sides → unchanged
  • ✅ 0-byte file (empty-local-only.txt) doesn't crash the hasher; correctly placed in new_local
  • ✅ 4-level nested path (deeply/nested/path/deep.txt) recurses correctly
  • ✅ Remote-only subdirectory (archive/) surfaces all its files in new_remote
  • ✅ Local-only subdirectories (shared/, deeply/) don't make remote API calls

D. --local-dir path variants (all real binary)

# Input Result
D1 local/ (trailing slash) ✅ accepted, 13 items
D2 ./local (dot-slash prefix) ✅ accepted, 13 items
D3 . (cwd itself) ✅ accepted, walks all of cwd

E. Path validation errors (real binary)

All return structured validation errors with the --local-dir flag name in the message (not the framework default --file):

# Input Error type Snippet
E1 --local-dir /etc validation --local-dir: --file must be a relative path within the current directory, got "/etc"
E2 --local-dir ../escape validation --local-dir: --file "../escape" resolves outside the current working directory
E3 --local-dir does-not-exist validation cannot read file: stat .../does-not-exist: no such file or directory
E4 --local-dir notadir.txt (regular file) validation --local-dir is not a directory: notadir.txt
E5 --local-dir $'lo\ncal' (newline char in name) validation resolves to a non-existent path → cannot read file: stat ...: no such file or directory (newline is a legal Unix filename char, so the path itself is well-formed and the error comes from the missing target)

F. Required / token validation

# Input Result
F1 omit --local-dir cobra: Error: required flag(s) "local-dir" not set
F2 omit --folder-token cobra: Error: required flag(s) "folder-token" not set
F3 --local-dir "" validation: --local-dir is required
F4 --folder-token "" validation: --folder-token is required
F5 --folder-token "tok\nwithnewline" validation: --folder-token contains invalid characters
F6 --folder-token "BOGUS_TOKEN_xxxxx" (passes format, fails API) api_error code 1061002 surfaced through structured envelope with log_id and troubleshooter URL
F7 Missing scope (initial drive:drive attempt before scope narrowing) missing_scope with auth login hint — confirms framework checks token scopes before issuing the API call

G. Dry-run

# Test Result
G1 --dry-run ✅ Prints description + GET /open-apis/drive/v1/files + folder_token summary; no real API calls

H. Output schema

# Check Result
H1 Top-level envelope keys {ok, identity, data}
H2 Data keys {new_local, new_remote, modified, unchanged}
H3 new_local[*] schema {rel_path} (no file_token, since the file isn't in Drive)
H4 new_remote / modified / unchanged[*] schema {rel_path, file_token}
H5 rel_path separator always forward-slash regardless of OS path style

@github-actions github-actions Bot added domain/ccm PR touches the ccm domain size/M Single-domain feat or fix with limited business impact labels Apr 28, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

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

Adds a new drive +status shortcut that compares a provided local manifest of files (relative to --local-dir) against a Drive folder (--folder-token) using SHA‑256 hashes, classifying files into new_local, new_remote, modified, and unchanged, and emitting JSON results.

Changes

Cohort / File(s) Summary
New Shortcut Implementation
shortcuts/drive/drive_status.go
Introduces exported DriveStatus command. Validates --local-dir, --folder-token, and non-empty --files-from. Normalizes/de‑dupes manifest paths, hashes local files (SHA‑256), recursively lists Drive files (paginated), downloads remote files for hashing, and emits JSON buckets: new_local, new_remote (with file_token), modified, unchanged. Dry-run returns Drive-listing shape; runtime prints progress to stderr.
Tests for Shortcut
shortcuts/drive/drive_status_test.go
Adds unit tests using temp files and HTTP stubs for Drive listing/download. Verifies classification buckets and tokens, ignores non‑file Drive entries, and checks validation errors for missing/non-directory --local-dir and blank --files-from. Includes tests for normalizeManifestPath.
Registry Update & Test
shortcuts/drive/shortcuts.go, shortcuts/drive/shortcuts_test.go
Registers DriveStatus in the drive shortcuts registry and updates the registry test to expect +status.
Documentation
skills/lark-drive/SKILL.md, skills/lark-drive/references/lark-drive-status.md
Adds documentation for drive +status: behavior, input rules (manifest normalization, no local tree walk), output JSON shape, Drive traversal rules (only type=file), required OAuth scopes, and usage examples/notes on network impact.

Sequence Diagram

sequenceDiagram
    participant User
    participant Cmd as "drive +status"
    participant LocalFS as "Local Filesystem"
    participant DriveAPI as "Drive API"
    participant Hasher as "SHA-256 Hasher"

    User->>Cmd: run with --local-dir, --folder-token, --files-from
    Cmd->>Cmd: validate inputs
    Cmd->>LocalFS: read manifest lines -> open files
    LocalFS-->>Hasher: stream file bytes
    Hasher-->>Cmd: local hashes by rel_path

    Cmd->>DriveAPI: list folder contents (paginated, recursive)
    DriveAPI-->>Cmd: file entries (only type=="file")
    Cmd->>Cmd: map remote rel_path -> file_token

    loop for each rel_path in intersection
        Cmd->>DriveAPI: download remote file (file_token)
        DriveAPI-->>Hasher: stream remote bytes
        Hasher-->>Cmd: remote hash
        Cmd->>Cmd: compare -> modified / unchanged
    end

    Cmd->>Cmd: mark new_local and new_remote
    Cmd-->>User: output JSON {new_local,new_remote,modified,unchanged}
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • liujinkun2025

Poem

🐰 I hop through paths, I sniff each file's tone,
I hash the bytes where seeds of truth are sown,
I match the cloud with what sits on the ground,
I mark what's new, what's changed, what's safe and sound,
A tiny rabbit keeping file lists known.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 accurately summarizes the main change: a new drive +status shortcut that performs content-hash-based differentiation between local files and a Drive folder.
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.
Description check ✅ Passed The PR description comprehensively covers all required template sections: summary, changes, test plan with checkboxes and detailed coverage, and related issues. Exceeds minimum requirements with extensive design notes, output schema, and validation test matrix.

✏️ 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/drive-status

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 and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

❌ Patch coverage is 71.51515% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.58%. Comparing base (0bbd0f2) to head (32eaf8a).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
shortcuts/drive/drive_status.go 71.34% 27 Missing and 20 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #692      +/-   ##
==========================================
+ Coverage   63.55%   63.58%   +0.03%     
==========================================
  Files         497      498       +1     
  Lines       42455    42620     +165     
==========================================
+ Hits        26984    27102     +118     
- Misses      13129    13156      +27     
- Partials     2342     2362      +20     

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
shortcuts/drive/shortcuts_test.go (1)

8-47: Consider asserting the exact shortcut order.

This membership-only check will miss regressions where Shortcuts() returns the right set in the wrong order. If the registry order is user-visible, a slice-by-slice assertion would be safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/drive/shortcuts_test.go` around lines 8 - 47, The test only checks
membership and misses order regressions; change
TestShortcutsIncludesExpectedCommands to assert the exact ordering by extracting
commands from Shortcuts() (use the existing got variable: iterate over got to
build a []string of commands) and compare that slice to want (e.g., with
reflect.DeepEqual or cmp.Diff) instead of only checking lengths and a map of
seen; keep the duplicate-check but replace the final membership loop with an
ordered slice equality assertion so the test fails if Shortcuts() returns the
correct items in the wrong order.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@shortcuts/drive/drive_status_test.go`:
- Around line 135-173: Add tests that exercise the folder-token validation path
for the DriveStatus command: create one test that omits --folder-token and
asserts mountAndRunDrive(DriveStatus, ["+status","--local-dir", tmpDir,
"--as","bot"], ...) returns a validation error, and another test that supplies
an invalid/ malformed --folder-token (e.g. empty string or bad format) and
asserts the error message mentions folder-token format; follow the pattern and
helpers used by TestDriveStatusRejectsMissingLocalDir and
TestDriveStatusRejectsLocalFile (use withDriveWorkingDir, mountAndRunDrive,
DriveStatus and the same argument style) so the new tests cover required and
format validation for --folder-token.

In `@shortcuts/drive/drive_status.go`:
- Around line 76-81: The DryRun endpoint for the Drive shortcut (the DryRun func
in drive_status.go) is missing a required E2E dry-run test; add a new dry-run
E2E test under tests/cli_e2e/dryrun/ (or the drive domain test dir) that
exercises the CLI in dry-run mode for this shortcut: invoke the CLI command with
--dry-run plus the same flags used by the DryRun implementation (e.g.,
--local-dir and --folder-token), assert the tool performs the expected dry-run
HTTP request to GET /open-apis/drive/v1/files with the folder_token query param,
and validate the dry-run output contains the descriptive text from Desc; follow
the existing dry-run test harness patterns in the repo for setup/teardown and
request assertions.

---

Nitpick comments:
In `@shortcuts/drive/shortcuts_test.go`:
- Around line 8-47: The test only checks membership and misses order
regressions; change TestShortcutsIncludesExpectedCommands to assert the exact
ordering by extracting commands from Shortcuts() (use the existing got variable:
iterate over got to build a []string of commands) and compare that slice to want
(e.g., with reflect.DeepEqual or cmp.Diff) instead of only checking lengths and
a map of seen; keep the duplicate-check but replace the final membership loop
with an ordered slice equality assertion so the test fails if Shortcuts()
returns the correct items in the wrong order.
🪄 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: 84a2de7a-6eff-4fe6-b431-b1a41e75310a

📥 Commits

Reviewing files that changed from the base of the PR and between 2e4cfb4 and 4ab562c.

📒 Files selected for processing (4)
  • shortcuts/drive/drive_status.go
  • shortcuts/drive/drive_status_test.go
  • shortcuts/drive/shortcuts.go
  • shortcuts/drive/shortcuts_test.go

Comment thread shortcuts/drive/drive_status_test.go
Comment thread shortcuts/drive/drive_status.go
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 28, 2026

🚀 PR Preview Install Guide

🧰 CLI update

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

🧩 Skill update

npx skills add larksuite/cli#feat/drive-status -y -g

@github-actions github-actions Bot added size/L Large or sensitive change across domains or core paths and removed size/M Single-domain feat or fix with limited business impact labels Apr 28, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
shortcuts/drive/drive_status.go (1)

75-80: ⚠️ Potential issue | 🟠 Major

Please confirm required dry-run E2E coverage for drive +status.

This shortcut adds new behavior and DryRun shape, but I can’t verify an accompanying dry-run E2E test from the provided diff context. Please confirm it exists under tests/cli_e2e/dryrun/ (or drive domain equivalent).

#!/bin/bash
# Check for dry-run E2E coverage of drive +status
fd -i '_test.go' tests/cli_e2e
rg -n --type=go --iglob '*_test.go' 'drive\s+\+status|--local-dir|--folder-token|--dry-run' tests/cli_e2e

Based on learnings "Dry-run E2E tests are required for every shortcut change; place in tests/cli_e2e/dryrun/ or corresponding domain directory".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/drive/drive_status.go` around lines 75 - 80, Add or confirm a
dry-run E2E test that covers the new DryRun shape for the drive +status
shortcut: create/verify a test under tests/cli_e2e/dryrun (or a drive domain
subdir) that invokes the CLI with drive +status and the flags --local-dir and
--folder-token and asserts the DryRun HTTP GET to /open-apis/drive/v1/files with
folder_token is produced; reference the DryRun function and its Desc text in
shortcuts/drive/drive_status.go to ensure the test exercises the exact dry-run
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@shortcuts/drive/drive_status.go`:
- Around line 75-80: Add or confirm a dry-run E2E test that covers the new
DryRun shape for the drive +status shortcut: create/verify a test under
tests/cli_e2e/dryrun (or a drive domain subdir) that invokes the CLI with drive
+status and the flags --local-dir and --folder-token and asserts the DryRun HTTP
GET to /open-apis/drive/v1/files with folder_token is produced; reference the
DryRun function and its Desc text in shortcuts/drive/drive_status.go to ensure
the test exercises the exact dry-run behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8e8d7c01-362d-45b0-8ed1-68034e2f4bc1

📥 Commits

Reviewing files that changed from the base of the PR and between 1e57eea and ea6cd37.

📒 Files selected for processing (1)
  • shortcuts/drive/drive_status.go

@fangshuyu-768 fangshuyu-768 changed the title feat(drive): add +status shortcut for hash-based local↔Drive diff feat(drive): add +status shortcut for content-hash diff Apr 28, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@shortcuts/drive/drive_status.go`:
- Around line 206-209: The branch that now only checks strings.HasPrefix(p,
"../") should also reject a bare ".." when cleanRoot is non-empty: add a guard
that returns "" when p == ".." in the same conditional block so that callers
(e.g., where filepath.Join(cleanRoot, p) is used) don't get a stray ".."
returned; update the logic around the p variable in drive_status.go to mirror
the earlier check for p == ".." when cleanRoot == "." or "" so both cases
consistently reject directory traversal.
🪄 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: 2bdde10a-c4b2-459c-b78a-eeb886e9046f

📥 Commits

Reviewing files that changed from the base of the PR and between ea6cd37 and d23598f.

📒 Files selected for processing (6)
  • shortcuts/drive/drive_status.go
  • shortcuts/drive/drive_status_test.go
  • shortcuts/drive/shortcuts.go
  • shortcuts/drive/shortcuts_test.go
  • skills/lark-drive/SKILL.md
  • skills/lark-drive/references/lark-drive-status.md
✅ Files skipped from review due to trivial changes (3)
  • shortcuts/drive/shortcuts.go
  • shortcuts/drive/shortcuts_test.go
  • skills/lark-drive/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • shortcuts/drive/drive_status_test.go
  • skills/lark-drive/references/lark-drive-status.md

Comment thread shortcuts/drive/drive_status.go Outdated
@fangshuyu-768 fangshuyu-768 force-pushed the feat/drive-status branch 2 times, most recently from 37f6fb3 to 482485b Compare April 28, 2026 10:47
fangshuyu-768 added a commit that referenced this pull request Apr 28, 2026
Addresses two CodeRabbit review comments on PR #692:

- Adds TestDriveStatusRejectsEmptyFolderToken and
  TestDriveStatusRejectsMalformedFolderToken so the Validate-stage
  required-check and the ResourceName format guard for --folder-token
  are exercised, not just --local-dir.

- Adds tests/cli_e2e/drive/drive_status_dryrun_test.go which drives
  the real binary in dry-run mode and asserts:

  * the request shape (GET /open-apis/drive/v1/files with
    folder_token in the dry-run envelope), plus the description text,
  * --local-dir absolute paths are rejected by Validate (which still
    runs under --dry-run) with --local-dir surfaced in the message,
  * cobra's required-flag enforcement rejects a missing
    --folder-token before any custom validation.
Adds `drive +status`, a read-only diff primitive that walks --local-dir,
recursively lists --folder-token, and reports four buckets — new_local,
new_remote, modified, unchanged — by SHA-256 content hash.

Implementation notes:

- Drive's list/metas APIs do not expose a content hash, so files
  present on both sides are downloaded via DoAPIStream and hashed in
  memory (sha256 + io.Copy, no disk write). Files only on one side are
  not fetched. The command stays Risk: "read".

- Only Drive entries with type=file participate. Online docs (docx,
  sheet, bitable, mindnote, slides) and shortcuts are skipped — there
  is no equivalent local binary to hash against.

- --local-dir is funneled through the framework's
  validate.SafeLocalFlagPath helper so that absolute paths and any ..
  that escapes cwd are rejected with --local-dir in the error message
  (rather than the internal default --file). FileIO().Stat() then
  enforces existence and the IsDir check.

- Local walk uses filepath.WalkDir behind a //nolint:forbidigo comment.
  The runtime FileIO interface has no walker today and shortcuts can't
  import internal/vfs; SafeInputPath has already bounded the walk root
  inside cwd, so the bare walk is acceptable until a runtime-level
  walker lands.

- Scopes: drive:drive.metadata:readonly (list folders) +
  drive:file:download (fetch files for hashing). The broader
  drive:drive scope is disabled by enterprise policy in some tenants;
  this narrower pair was verified end-to-end.

Tests cover the four-bucket categorization with a nested subfolder and
docx/shortcut filtering, plus validation errors for missing local-dir,
non-directory local-dir, and absolute-path local-dir.
Adds references/lark-drive-status.md covering parameters, output
schema, the type=file scoping rule, and the network-traffic caveat
(hash is streamed in memory, but bytes still cross the wire).

Notes that --local-dir is bounded to cwd by the CLI's path validation,
and that when a user wants to compare a directory outside cwd the
agent should ask the user to relocate or to switch the agent's working
directory rather than `cd`-ing on its own.

Wires +status into the Shortcuts table in SKILL.md.
Addresses two CodeRabbit review comments on PR #692:

- Adds TestDriveStatusRejectsEmptyFolderToken and
  TestDriveStatusRejectsMalformedFolderToken so the Validate-stage
  required-check and the ResourceName format guard for --folder-token
  are exercised, not just --local-dir.

- Adds tests/cli_e2e/drive/drive_status_dryrun_test.go which drives
  the real binary in dry-run mode and asserts:

  * the request shape (GET /open-apis/drive/v1/files with
    folder_token in the dry-run envelope), plus the description text,
  * --local-dir absolute paths are rejected by Validate (which still
    runs under --dry-run) with --local-dir surfaced in the message,
  * cobra's required-flag enforcement rejects a missing
    --folder-token before any custom validation.
….. escape

Reported in PR review: --local-dir was validated through
SafeLocalFlagPath, but the actual walk used the user-supplied raw
string. SafeLocalFlagPath returns the original value (it only checks
the path through SafeInputPath and discards the canonical form), and
SafeInputPath itself relies on filepath.Clean for path normalization.
filepath.Clean shrinks "link/.." to "." purely as string manipulation,
so the validator sees a path inside cwd. The kernel, however, resolves
"link/.." through the symlink target's parent — which is outside cwd
and is what filepath.WalkDir actually traverses.

Fix: in Execute, resolve --local-dir via validate.SafeInputPath to
get the canonical absolute path (this one fully evaluates symlinks
across the entire path), and walk that path. Each absolute walk hit
is converted to a cwd-relative form via filepath.Rel against
validate.SafeInputPath(".") so FileIO.Open's existing SafeInputPath
guard (which rejects absolute paths) still applies.

Adds TestDriveStatusDoesNotEscapeViaSymlinkParentRef as a regression:
it stages an "escape" sibling directory containing a sentinel file,
adds a "link" symlink in cwd pointing into the escape directory, and
runs +status with --local-dir "link/..". Without this fix, the raw
walk visits the sentinel and leaks it into new_local; with the fix,
the walk stays inside the canonical cwd.

A standalone repro confirms the underlying behavior: raw
filepath.WalkDir("link/..", ...) traversed dozens of unrelated files
in the kernel-resolved parent directory; walking the canonical root
visits only the legitimate cwd contents.
…atus

Adds two corner-case regressions to back up the canonical-root walk fix:

- TestDriveStatusSkipsSymlinkInsideRoot: a child symlink under
  --local-dir that points to a sibling temp dir outside cwd. WalkDir's
  default policy must report it as a non-regular entry so the callback
  skips it, and the sentinel inside the target must not surface in
  new_local. This pins the contract our caller relies on (walk
  declines to follow child symlinks even when the canonical root
  resolves cleanly).

- TestDriveStatusSurvivesCircularSymlinkInsideRoot: a child symlink
  pointing back at one of its ancestors. The walk must terminate and
  surface the legitimate sibling file; if WalkDir ever followed the
  loop, the per-test timeout would catch it.
fangshuyu-768 added a commit that referenced this pull request Apr 28, 2026
Adds `drive +pull`, a one-way Drive → local mirror command. It
recursively lists --folder-token, downloads each type=file entry
into --local-dir at the matching relative path, and optionally
deletes local files absent from the remote (mirror semantics).

Implementation notes:

- Listing recurses through subfolders with the standard 200-page
  pagination loop. Online docs (docx, sheet, bitable, mindnote,
  slides) and shortcuts are skipped since there is no equivalent
  local binary to write back. Folder tree is reproduced under
  --local-dir, with parent directories auto-created by FileIO.Save.

- Per-file --if-exists=overwrite (default) | skip controls how
  pre-existing local files are treated; the framework's enum guard
  rejects any other value.

- --delete-local is the only destructive flag and is bound to --yes
  in Validate: --delete-local without --yes is rejected upfront so
  no listing or download even runs. --delete-local --yes performs
  downloads first, then walks --local-dir and removes regular files
  not present in the remote map. This matches the spec doc's
  "high-risk-write" intent for --delete-local without making the
  default pull path require confirmation.

- --local-dir is funneled through validate.SafeLocalFlagPath so
  errors reference --local-dir instead of the framework default
  --file. FileIO().Stat then enforces existence and IsDir.

- Scopes: drive:drive.metadata:readonly + drive:file:download. The
  broader drive:drive is disabled by enterprise policy in some
  tenants.

- Listing helper (drivePullListRemote) is duplicated locally rather
  than reused from drive_status.go because that change is still in
  open PR #692; once it merges, both can be lifted into a shared
  drive package helper. TODO marker is left in the code.

Tests cover six unit scenarios (happy-path with nested subfolder +
docx skipping, --if-exists=skip, --delete-local rejection without
--yes, --delete-local --yes deletes orphans, absolute-path
rejection, bad enum) and four E2E dry-run scenarios (request shape,
absolute path rejection, --delete-local --yes guard, missing
required flag).
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/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant