Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
e488cf4
feat(qualitygate): enforce six-layer package dependency boundaries
sang-neo03 Jul 24, 2026
acd50f2
fix(qualitygate): harden layering ratchet enforcement
sang-neo03 Jul 24, 2026
1772afe
fix(qualitygate): cover release build graphs
sang-neo03 Jul 24, 2026
c09b0d5
test(qualitygate): tolerate coverage helper diagnostics
sang-neo03 Jul 24, 2026
f1ce88b
test(qualitygate): pin release target coverage
sang-neo03 Jul 24, 2026
59b6393
test(qualitygate): fail closed on release target drift
sang-neo03 Jul 24, 2026
cbe0fb1
test(qualitygate): reject unsupported release variants
sang-neo03 Jul 24, 2026
7c2ca4e
test(qualitygate): cover default release graph
sang-neo03 Jul 24, 2026
abe0d09
fix(qualitygate): harden layering edge parsing and release-target checks
sang-neo03 Jul 24, 2026
d48c218
fix: close layering quality gate gaps
sang-neo03 Jul 24, 2026
8203055
fix(qualitygate): scope the examples exemption to wrapper mains
sang-neo03 Jul 25, 2026
217f4e5
fix(qualitygate): pin the examples surface with an allowlist
sang-neo03 Jul 25, 2026
8ba2431
refactor(extension): remove internal package dependencies
sang-neo03 Jul 25, 2026
d62f8dc
refactor(events): move message conversion below shortcuts
sang-neo03 Jul 25, 2026
5e23bbe
refactor(auth): extract authentication logging from keychain
sang-neo03 Jul 25, 2026
5157c3a
refactor(binding): split audit and config responsibilities
sang-neo03 Jul 25, 2026
aa50bec
refactor(credential): share the brand parser across providers
sang-neo03 Jul 25, 2026
2d1341a
refactor(convertlib): drop the unused interactive-content wrapper
sang-neo03 Jul 25, 2026
85490cb
fix(authlog): share one authentication logger per process
sang-neo03 Jul 25, 2026
602f671
chore(qualitygate): align the bootstrap baseline with the merged regi…
sang-neo03 Jul 25, 2026
e5b2e96
Merge branch 'main' into refactor/package-debt-phase2
sang-neo03 Jul 25, 2026
aec9c46
fix(authlog): allow the cleanup panic notice and cover the logger
sang-neo03 Jul 27, 2026
72ea028
Merge remote-tracking branch 'origin/main' into refactor/package-debt…
sang-neo03 Jul 27, 2026
4073e75
fix(qualitygate): exempt single edges instead of whole packages
sang-neo03 Jul 27, 2026
b028c33
fix(authlog): keep the installed logger for the life of the process
sang-neo03 Jul 27, 2026
bf56e90
docs: point the two brand parsers at each other
sang-neo03 Jul 27, 2026
52a1187
test(qualitygate): detect build tags the union never scans
sang-neo03 Jul 27, 2026
475f04a
fix(authlog): stop trusting argument position, and speak up on a reje…
sang-neo03 Jul 27, 2026
2662729
test(core): enforce the brand parity the comments only request
sang-neo03 Jul 27, 2026
c138f29
Merge remote-tracking branch 'origin/main' into refactor/package-debt…
sang-neo03 Jul 27, 2026
97e397c
refactor(sidecar): return typed errors from the demo servers
sang-neo03 Jul 27, 2026
57bf8cc
docs(authlog): record why two neighbours read the environment differe…
sang-neo03 Jul 27, 2026
c12ab91
fix(authlog): restore the word cap the flag boundary replaced
sang-neo03 Jul 27, 2026
1af34e6
docs(authlog): correct what the word cap is measuring
sang-neo03 Jul 27, 2026
7b6962a
fix(sidecar): keep the classification the resolver already made
sang-neo03 Jul 27, 2026
9c50045
docs(authlog): drop the pointer to a note that was never written
sang-neo03 Jul 27, 2026
2634092
refactor(shortcuts): route scope checks through common
sang-neo03 Jul 28, 2026
770c230
refactor(shortcuts): route client helpers through common
sang-neo03 Jul 28, 2026
342d1a2
refactor(shortcuts): centralize output directory creation
sang-neo03 Jul 28, 2026
38312d3
refactor(apps): move local state behind internal storage
sang-neo03 Jul 28, 2026
9d8e93c
refactor(core): extract risk constants
sang-neo03 Jul 28, 2026
3732c6b
refactor(core): extract brand package
sang-neo03 Jul 28, 2026
ee27d0c
refactor(core): extract workspace paths
sang-neo03 Jul 28, 2026
c69a61c
refactor(core): extract secret storage
sang-neo03 Jul 28, 2026
939fc1e
refactor(core): extract identity policy
sang-neo03 Jul 28, 2026
0266a7e
refactor(core): rename remaining config package
sang-neo03 Jul 28, 2026
d7cf797
refactor(config): remove unreachable legacy loaders
sang-neo03 Jul 28, 2026
5bae5bb
fix(lint): follow the extracted brand resolver
sang-neo03 Jul 28, 2026
f25ef0a
docs: point the moved core references at their new packages
sang-neo03 Jul 28, 2026
bb7342c
docs: record what internal/core became in the source layout
sang-neo03 Jul 28, 2026
1ba4d1f
Merge remote-tracking branch 'origin/main' into refactor/package-debt…
sang-neo03 Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
13 changes: 0 additions & 13 deletions .github/workflows/arch-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,6 @@ jobs:
go list -m -u all 2>/dev/null | grep '\[' >> report.md || echo "All dependencies up to date" >> report.md
echo '```' >> report.md

- name: Circular dependency check
run: |
echo "## Circular Dependencies" >> report.md
go list -f '{{.ImportPath}} {{join .Imports " "}}' ./... | \
go run golang.org/x/tools/cmd/digraph@v0.31.0 scc 2>&1 | tee cycles.txt
if [ -s cycles.txt ]; then
echo '```' >> report.md
cat cycles.txt >> report.md
echo '```' >> report.md
else
echo "No circular dependencies detected." >> report.md
fi

- name: E2E coverage gaps
run: |
echo "## E2E Coverage Gaps" >> report.md
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ jobs:
env:
QUALITY_GATE_CHANGED_FROM: ${{ github.event.pull_request.base.sha || github.event.before || 'origin/main' }}
run: echo "QUALITY_GATE_CHANGED_FROM=$(bash scripts/resolve-changed-from.sh)" >> "$GITHUB_ENV"
- name: Enforce layering ratchet
run: bash scripts/check-layering-ratchet.sh "$QUALITY_GATE_CHANGED_FROM"
- name: Run golangci-lint
run: go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 run --new-from-rev="$QUALITY_GATE_CHANGED_FROM"
- name: Run source-contract lint guards (lintcheck)
Expand Down
13 changes: 12 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,21 @@ Both notices recommend the same fix command: `lark-cli update`. The skills notic
| `internal/credential/` | Credential provider chain (extension → default) |
| `extension/credential/` | Plugin-facing credential interfaces and env provider |
| `internal/client/client.go` | APIClient: DoSDKRequest, DoStream |
| `internal/core/config.go` | Multi-profile config loading/saving |
| `brand/` | Brand (feishu/lark) and its endpoint hosts — repo root, so `extension/` may import it |
| `internal/workspace/` | Workspace detection plus the config and runtime directory paths |
| `internal/identity/` | The `--as` identity (user/bot) and the strict-mode policy |
| `internal/config/config.go` | Multi-profile config loading/saving |
| `internal/vfs/` | Filesystem abstraction (use `vfs.*` instead of `os.*`) |
| `internal/validate/path.go` | Path safety validation |

`internal/core` is gone. Besides the four packages above it also became
`internal/secret` (app secret storage and resolution) and `internal/risk` (the
read / write / high-risk-write vocabulary). Import the narrowest one you need:
`brand`, `internal/workspace`, `internal/identity`, `internal/secret` and
`internal/risk` do not import each other — only `internal/config` sits on top of
them — so asking for a config directory no longer drags in keychain, i18n and
validate.

## Who Uses This CLI

This CLI's primary consumers include AI agents (Claude Code, Cursor, Gemini CLI). Your code is read by machines — error messages, output format, and flag design all directly affect agent success rates.
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ fmt-check:

script-test:
bash scripts/resolve-changed-from.test.sh
bash scripts/check-layering-ratchet.test.sh
bash scripts/ci-workflow.test.sh
bash scripts/semantic-review-workflow.test.sh
$(NODE) --test scripts/e2e_domains.test.js scripts/fetch_e2e_tat.test.js scripts/install.test.js scripts/release-preflight.test.js scripts/semantic-review-verify-artifact.test.js scripts/pr-quality-summary.test.js scripts/semantic-review-publish.test.js scripts/ci-quality-summary-publish.test.js
Expand Down
26 changes: 13 additions & 13 deletions internal/core/types.go → brand/brand.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT

package core
package brand

import "strings"

// LarkBrand represents the Lark platform brand.
// Brand represents the Lark platform brand.
// "feishu" targets China-mainland, "lark" targets international.
// ParseBrand and ResolveEndpoints map unrecognized values to BrandFeishu.
type LarkBrand string
// ParseBrand and ResolveEndpoints map unrecognized values to Feishu.
type Brand string

const (
BrandFeishu LarkBrand = "feishu"
BrandLark LarkBrand = "lark"
Feishu Brand = "feishu"
Lark Brand = "lark"
)

// ParseBrand normalizes a brand string (case-insensitive, whitespace-tolerant);
// anything other than "lark" normalizes to BrandFeishu.
func ParseBrand(value string) LarkBrand {
// anything other than "lark" normalizes to Feishu.
func ParseBrand(value string) Brand {
if strings.ToLower(strings.TrimSpace(value)) == "lark" {
return BrandLark
return Lark
}
return BrandFeishu
return Feishu
}

// OAuthTokenV3Path is the unified OAuth 2.0 Token Endpoint path on the accounts
Expand All @@ -40,9 +40,9 @@ type Endpoints struct {

// ResolveEndpoints resolves endpoint URLs for the brand, normalizing its
// input so stored values with unusual casing still resolve correctly.
func ResolveEndpoints(brand LarkBrand) Endpoints {
func ResolveEndpoints(brand Brand) Endpoints {
switch ParseBrand(string(brand)) {
case BrandLark:
case Lark:
return Endpoints{
Open: "https://open.larksuite.com",
Accounts: "https://accounts.larksuite.com",
Expand All @@ -60,6 +60,6 @@ func ResolveEndpoints(brand LarkBrand) Endpoints {
}

// ResolveOpenBaseURL returns the Open API base URL for the given brand.
func ResolveOpenBaseURL(brand LarkBrand) string {
func ResolveOpenBaseURL(brand Brand) string {
return ResolveEndpoints(brand).Open
}
30 changes: 15 additions & 15 deletions internal/core/types_test.go → brand/brand_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT

package core
package brand

import "testing"

func TestResolveEndpoints_Feishu(t *testing.T) {
ep := ResolveEndpoints(BrandFeishu)
ep := ResolveEndpoints(Feishu)
if ep.Open != "https://open.feishu.cn" {
t.Errorf("Open = %q, want feishu.cn", ep.Open)
}
Expand All @@ -22,7 +22,7 @@ func TestResolveEndpoints_Feishu(t *testing.T) {
}

func TestResolveEndpoints_Lark(t *testing.T) {
ep := ResolveEndpoints(BrandLark)
ep := ResolveEndpoints(Lark)
if ep.Open != "https://open.larksuite.com" {
t.Errorf("Open = %q, want larksuite.com", ep.Open)
}
Expand Down Expand Up @@ -50,26 +50,26 @@ func TestResolveEndpoints_EmptyDefaultsToFeishu(t *testing.T) {
}

func TestResolveOpenBaseURL(t *testing.T) {
if got := ResolveOpenBaseURL(BrandFeishu); got != "https://open.feishu.cn" {
if got := ResolveOpenBaseURL(Feishu); got != "https://open.feishu.cn" {
t.Errorf("ResolveOpenBaseURL(feishu) = %q", got)
}
if got := ResolveOpenBaseURL(BrandLark); got != "https://open.larksuite.com" {
if got := ResolveOpenBaseURL(Lark); got != "https://open.larksuite.com" {
t.Errorf("ResolveOpenBaseURL(lark) = %q", got)
}
}

func TestParseBrand(t *testing.T) {
cases := []struct {
in string
want LarkBrand
want Brand
}{
{"", BrandFeishu},
{"feishu", BrandFeishu},
{"lark", BrandLark},
{"LARK", BrandLark},
{" lark ", BrandLark},
{"Lark", BrandLark},
{"xyz", BrandFeishu},
{"", Feishu},
{"feishu", Feishu},
{"lark", Lark},
{"LARK", Lark},
{" lark ", Lark},
{"Lark", Lark},
{"xyz", Feishu},
}
for _, c := range cases {
if got := ParseBrand(c.in); got != c.want {
Expand All @@ -83,11 +83,11 @@ func TestParseBrand(t *testing.T) {
// unusual casing or whitespace still resolve to their intended endpoints.
func TestResolveEndpoints_NormalizesBrand(t *testing.T) {
for _, raw := range []string{"LARK", " lark ", "Lark"} {
if got := ResolveEndpoints(LarkBrand(raw)).Open; got != "https://open.larksuite.com" {
if got := ResolveEndpoints(Brand(raw)).Open; got != "https://open.larksuite.com" {
t.Errorf("ResolveEndpoints(%q).Open = %q, want the lark endpoint", raw, got)
}
}
if got := ResolveEndpoints(LarkBrand("unexpected")).Open; got != "https://open.feishu.cn" {
if got := ResolveEndpoints(Brand("unexpected")).Open; got != "https://open.feishu.cn" {
t.Errorf("ResolveEndpoints(unexpected).Open = %q, want the feishu default", got)
}
}
9 changes: 5 additions & 4 deletions cmd/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import (
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/internal/client"
"github.com/larksuite/cli/internal/cmdutil"
"github.com/larksuite/cli/internal/core"
configpkg "github.com/larksuite/cli/internal/config"
"github.com/larksuite/cli/internal/identity"
"github.com/larksuite/cli/internal/output"
"github.com/larksuite/cli/internal/validate"
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
Expand All @@ -33,7 +34,7 @@ type APIOptions struct {
// Flags
Params string
Data string
As core.Identity
As identity.Identity
Output string
PageAll bool
PageSize int
Expand Down Expand Up @@ -87,7 +88,7 @@ Examples:
opts.Path = args[1]
opts.Cmd = cmd
opts.Ctx = cmd.Context()
opts.As = core.Identity(asStr)
opts.As = identity.Identity(asStr)
if runF != nil {
return runF(opts)
}
Expand Down Expand Up @@ -304,7 +305,7 @@ func apiRun(opts *APIOptions) error {
return nil
}

func apiDryRun(f *cmdutil.Factory, request client.RawApiRequest, config *core.CliConfig, opts *APIOptions) error {
func apiDryRun(f *cmdutil.Factory, request client.RawApiRequest, config *configpkg.CliConfig, opts *APIOptions) error {
return cmdutil.PrintDryRun(request, config, dryRunOutputOptions(f, opts))
}

Expand Down
10 changes: 6 additions & 4 deletions cmd/api/api_paginate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import (
"net/http"
"testing"

"github.com/larksuite/cli/brand"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/internal/client"
"github.com/larksuite/cli/internal/cmdutil"
"github.com/larksuite/cli/internal/core"
configpkg "github.com/larksuite/cli/internal/config"
"github.com/larksuite/cli/internal/httpmock"
"github.com/larksuite/cli/internal/identity"
"github.com/larksuite/cli/internal/output"
)

Expand All @@ -44,10 +46,10 @@ func newAPIPaginateTestHarness(t *testing.T) (*client.APIClient, *bytes.Buffer,
output.PendingNotice = nil
t.Cleanup(func() { output.PendingNotice = previousNotice })

config := &core.CliConfig{
config := &configpkg.CliConfig{
AppID: "test-app",
AppSecret: "test-secret",
Brand: core.BrandFeishu,
Brand: brand.Feishu,
}
f, out, errOut, reg := cmdutil.TestFactory(t, config)
ac, err := f.NewAPIClientWithConfig(config)
Expand All @@ -62,7 +64,7 @@ func apiPaginateRequest() client.RawApiRequest {
return client.RawApiRequest{
Method: "GET",
URL: "/open-apis/test/v1/items",
As: core.AsBot,
As: identity.AsBot,
}
}

Expand Down
Loading
Loading