Files
larksuite-cli/internal/skillscheck/stamp.go
niuchong 8f410ab140 feat: add skills version drift notice and unify update flow (#723)
Users who install or upgrade lark-cli via make install, go install, or
direct binary download end up with a binary but no AI agent skills,
degrading agent UX. This PR adds a startup-time skills version drift
notice (injected into JSON envelope _notice.skills, mirroring the
existing _notice.update pattern) and unifies lark-cli update's skills
sync across all three branches (npm / manual / already-latest) with
stamp-based dedup, so any explicit update invocation keeps skills in
sync regardless of how the binary was installed.

Changes:
- new internal/skillscheck package: notice (StaleNotice + atomic
  pending), stamp (~/.lark-cli/skills.stamp), skip (CI / DEV /
  non-release / LARKSUITE_CLI_NO_SKILLS_NOTIFIER opt-out), check
  (synchronous Init)
- cmd/root.go: rename setupUpdateNotice -> setupNotices, compose
  output.PendingNotice returning {update?, skills?}; capture
  build.Version locally before spawning the async update goroutine
- cmd/update/update.go: add runSkillsAndStamp helper with stamp-based
  dedup; rewire the three branches through shared applySkillsResult /
  emitSkillsTextHints helpers; add skills_status block to --check JSON
  output as a pure report (no side effects)
- internal/update: export IsRelease(version) bool / IsCIEnv() bool
  for cross-package reuse; refresh UpdateInfo.Message to append
  ', run: lark-cli update' so both notices recommend the same fix
- AGENTS.md: add Notification Opt-Outs section documenting
  LARKSUITE_CLI_NO_UPDATE_NOTIFIER and LARKSUITE_CLI_NO_SKILLS_NOTIFIER
- internal/binding/types.go: bump default exec-provider timeout from
  5s to 10s (out-of-scope flake fix for TestResolveExecRef_JSONResponse
  under heavy parallel test load)
2026-05-07 10:52:35 +08:00

50 lines
1.4 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package skillscheck
import (
"errors"
"io/fs"
"path/filepath"
"strings"
"github.com/larksuite/cli/internal/core"
"github.com/larksuite/cli/internal/validate"
"github.com/larksuite/cli/internal/vfs"
)
const stampFile = "skills.stamp"
// stampPath returns ~/.lark-cli/skills.stamp.
// Uses the BASE config dir (not workspace-aware) because skills install
// globally via `npx -g`; per-workspace tracking would produce false
// drift signals when switching workspaces.
func stampPath() string {
return filepath.Join(core.GetBaseConfigDir(), stampFile)
}
// ReadStamp returns the version recorded in the stamp file. Returns
// ("", nil) when the file does not exist (interpreted as "never synced").
// Other I/O errors are returned as-is so callers can fail closed.
func ReadStamp() (string, error) {
data, err := vfs.ReadFile(stampPath())
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return "", nil
}
return "", err
}
return strings.TrimSpace(string(data)), nil
}
// WriteStamp records `version` as the last successfully synced skills
// version. Atomic via tmp + rename (validate.AtomicWrite). Creates
// the base config directory if it does not exist.
func WriteStamp(version string) error {
if err := vfs.MkdirAll(core.GetBaseConfigDir(), 0o700); err != nil {
return err
}
return validate.AtomicWrite(stampPath(), []byte(version), 0o644)
}