35 Commits

Author SHA1 Message Date
zhaojunlin0405 fda8d7cdae fix: reduce vulnerable dependencies while retaining Go 1.23 (#2659)
* fix: reduce vulnerable dependencies while retaining Go 1.23

* fix(imageconfig): own the standard-library codec registration

Decode dispatches PNG, JPEG and GIF to image.DecodeConfig, which only
answers for codecs some package in the binary has imported. The package
did not import them; it worked because all five call sites still carried
blank imports left over from calling image.DecodeConfig directly. Those
files no longer mention image at all, so the imports now read as dead
weight and the next tidy-up removes them -- silently for base, calendar
and doc-media, as a hard command failure for sheets +set-cell-image and
docs remote images.

Register the three codecs where they are used and drop the call-site
imports. The guard lives in deptest because that package imports no
codec of its own and can therefore prove the ownership.

* fix(imageconfig): keep WebP dimensions readable when the final pad byte is absent

readWebP required every chunk to fit inside the container *with* its
even-padding byte, and required the container size itself to be even,
before it looked at the chunk at all. A writer that omits the pad after
a final odd-sized chunk, or that counts trailing bytes in the RIFF size,
therefore lost its dimensions -- files golang.org/x/image reads without
complaint. That is a silent downgrade on the base, calendar and
doc-media paths and a hard failure on sheets +set-cell-image and docs
remote images, which surface the decode error to the user.

Separate the two bounds. The chunk payload must lie inside the
container, which still rejects a chunk claiming to reach past it; the
padding byte is only required where it is actually consumed, when
skipping to the next chunk.

Differential against x/image v0.30.0 over 300k mutated inputs: 168450
inputs accepted by both, zero dimension disagreements, and x/image-only
acceptances down from 4806 to 3442.

* test(imageconfig): reach the format readers when asserting error preservation

TestMetadataPreservesReadCause injected its failure at offset 0, which
Decode consumes for the magic bytes before it dispatches. readBMP and
readWebP were never entered, so both could discard the source error and
the test would still pass -- verified by mutation: making readBMP return
errMetadata instead of the read error leaves the old assertion green.

Inject at the first offset each reader requests on its own, and assert
the reader ran by checking the format it reports.

Raised by coderabbitai on internal/imageconfig/metadata_test.go.

* test(deptest): pin the binary's external package surface

Adding a module is visible: go.mod changes and the diff invites a look.
Adding a subpackage of a module already required is not. The diff is one
import line, go.mod is untouched, and the binary silently grows a new
package graph.

That is exactly how golang.org/x/net/idna entered this CLI -- via a
single httpguts import added in #1910 for a header check that turned out
to be redundant -- bringing three x/text packages with it. Nobody looked
until an advisory landed on idna. The enumerated guard added alongside
it only names the three packages already known to be a problem; it
cannot see the next one.

Record the non-stdlib package set of the release binary per GOOS and
diff against it. Replaying the #1910 import against this guard reports
the five packages it added, by name, on all three platforms. Regenerate
with -update-import-surface after confirming an addition is intended.

Also assert golang.org/x/image stays out of both the binary and the test
graph, which is what this branch set out to remove and what nothing
currently guards.

* fix(deptest): read only stdout when recording the import surface

The recorder used CombinedOutput, so "go: downloading ..." notices --
which go list writes to stderr -- were parsed as package names whenever
the module cache was cold for the platform being listed. It passed here
and failed on CI, which had never fetched the windows-only modules:
go-winio, coninput, mousetrap and go-localereader showed up as four
added packages.

Read stdout only, keep stderr for the failure message, and fail loudly
on any line containing whitespace, since an import path never does.

Verified against a cold GOMODCACHE: the download notice lands on stderr
and stdout stays clean.

* fix(imageconfig): ignore the VP8X reserved fields, as the spec requires

readWebP rejected a VP8X chunk whose reserved bits were non-zero: the
two high flag bits, the low flag bit, or the 24-bit reserved block. The
container spec says of each of them "MUST be 0. Readers MUST ignore this
field." Writing a non-zero value is the writer's violation; refusing to
read it is ours.

Reproduced against a real cwebp VP8X file: with any one reserved bit
set, golang.org/x/image reads 37x23 from both DecodeConfig and a full
pixel decode, while this reader returned an error -- which surfaces to
the user as a blocked docs image import or a failed sheets
+set-cell-image.

Keep the 10-byte chunk length and the container bounds, drop the
reserved-field check. The malformed-metadata case that pinned the old
behaviour now covers the chunk length instead.

---------
2026-09-11 00:14:28 +08:00
zhaojunlin0405 ad8766b616 feat: add lazy API catalog routing (#2232) 2026-09-10 13:29:26 +08:00
sang-neo03 6952d3aa7f feat(extension): add business command extension v1 (#2308)
* feat(shortcuts): import typed shortcut framework from c07b64621

* feat(extension): add public command contract

* feat(extension): compile and register business commands

* feat(cmd): assemble business command sets

* fix(auth): derive login domains from shortcuts

* test(extension): add business command test runtime

* ci: verify generated Go sources

* test(auth): match help and interactive domains

* fix(extension): validate command path segments

* fix(extension): satisfy generator and error guards

* fix(extension): follow generated domain naming contract

* fix(command): complete extension runtime contracts

* test(command): cover public extension surface

* fix(command): remove unused typed runtime APIs

* fix(command): address follow-up review findings

* fix(command): enforce extension v1 contracts

* fix(command): remove unreachable compatibility wrappers

* fix(auth): keep scope-less domains addressable via --domain, matching main

Move the declared-scope filter from allKnownDomains to the interactive
selector only. On main, a scope-less shortcut domain (event) passes
--domain validation and fails later with "no matching scopes found";
the previous unified filter changed that to "unknown domain" and
dropped it from the --help list. The interactive picker still hides
scope-less domains — selecting one can only fail.

* feat(command): add PathSegment for user-provided path values

Business code concatenates IDs into request paths but had no public
escape helper (internal/validate.EncodePathSegment is unreachable from
extension/command). Mirror its url.PathEscape semantics, use it in the
business command examples, and pin the traversal defense: the validator
decodes percent-encoding before the canonical check, so both raw and
escaped dot sequences fail same-origin validation.

* docs(command): add runnable chat-brief distribution example

Mirror the audit-observer precedent: a buildable wrapper main under
examples/ showing WithCommandSets against the real distribution shape
(plugins, strict mode, and service commands stay enabled). Covers the
single-read command (Validate, shared DryRun request, CallJSON,
PathSegment, Tips) and a Page[T] list command whose pagination flags
come from the compiler. The testdata/wrapper fixture stays test-only.

Verified offline: --help renders tag-driven parameters, +chat-brief-list
exposes --page-all/--page-limit/--page-delay, and --dry-run previews the
request with a fake env token and no network access.

* fix(command): normalize page envelopes and bound CollectAllPages

Two pagination contract fixes from the extension design (owner plan §8.3):

Page decoding accepted only a literal "items" array, so endpoints that
spell their list field differently (drive uses files, some responses use
records) walked every page while decoding nothing — CollectAllPages then
returned an empty set marked complete, and downstream writes ran against
it. Each page now normalizes its single top-level array field into
Page.Items; zero or multiple array fields fail closed with a typed
invalid-response error.

CollectAllPages previously reused the user-facing --page-limit maximum
(1000) as its walk bound. A complete-set collection holds every page in
memory before the workflow's writes run, so it now uses the design's
dedicated workflow bound of 100 pages.

* feat(commandhost): note bounded repetition in Page dry-run previews

A Page[T] command's dry-run can only show the first request; fabricating
response-dependent page tokens is forbidden. Append the bounded-repeat
explanation to the previewed request (preserving any business
description), matching the design's dry-run contract.

Also give the example's list command a --page-token resume flag seeded
into the request, documenting the resume convention: the framework owns
--page-all/--page-limit/--page-delay while the starting cursor is a
business-declared input, independent of --page-all.

* docs(command): generate domain constants with English comments

The generator emitted Chinese titles while the rest of the public
extension packages document in English. Switch the generator to the
"en" service title and regenerate. The generator already rejects a
domain missing either locale, so the switch keeps its own guard.

* docs(command): mark the host adapter read surface

The Host* types, InspectCommand, InspectDomain and CloneSets exist for
lark-cli's host adapter, not for business commands, but nothing said so
at the symbols themselves. They cannot move to a subpackage: a Command
holds its declaration unexported, so a sibling package has no way to
reach it, and moving the wire types to internal/ would cycle back
through CommandMetadata and CommandContext.

Also correct HostPagination, which is not adapter-only -- ContextOptions
and commandtest both carry it.

* refactor(command): type CommandMetadata.Service as DomainName

A set already declares its domain through ExtendDomain(DomainIm), yet
every command repeated the same domain as a bare string that only the
host compiler checked. Typing the field points authors at the generated
enum and forces an explicit conversion when the value comes from a
string variable.

This does not make a mistyped literal a compile error -- an untyped
constant still converts to DomainName -- so the mismatch check in
CompileSets stays the actual net. The example and the wrapper fixture
now declare command.DomainIm.

The chat-brief example was also not gofmt-clean, which the CI format
gate would have caught.

* refactor(command): extract the command-set assembly steps

buildInternalWithConfig is an orchestrator, and the business command
sets were compiled inline inside it. Move that step to
resolveShortcutSnapshot so the entry point reads as one call and the
built-in/external merge has a name.

newCommand carried six near-identical blocks that each nil-checked a
hook and wrapped it in the same type assertion. Split them into one
binder per hook shape; Normalize and Validate now share bindArgsHook
since their signatures match. The behaviour is unchanged: an undeclared
hook still erases to nil, and an empty renderer map still yields nil.

* perf(shortcuts): stop re-cloning an already-isolated snapshot

AllShortcuts deep-copies because a Shortcut carries slice fields whose
backing arrays a shallow copy would share: an external distribution
mutating registered[0].Flags[0] would corrupt the process-global list.
That copy is worth its ~165us over 500+ shortcuts.

Paying it four times per startup is not. auth, schema and the mount path
each cloned the snapshot again, but they receive it from
AllShortcutsWithExternal with no third-party code in between, and nothing
in this repository mutates a shortcut element -- mountDeclarative takes a
value receiver and only replaces slice headers. Drop those three copies
and document the boundary on AllShortcuts so the next reader does not
reintroduce them.

Startup drops from four full clones to one. Benchmarks pin the remaining
cost so a regression points at a new clone rather than at growth in the
shortcut set.

* fix(command): align the commandtest page bound and escape wrapper paths

Two review findings, both of which let a business command pass its tests
and then misbehave in production.

The commandtest recorder walked 1000 pages for a complete-set collection
while the host adapter stops at 100, so a command tested against 300
pages of fixtures would fail its first real --page-all run with
PaginationLimitError. The bound now lives in internal/pagination, which
both sides already import, and the hard-limit test scripts itself from
that constant instead of restating 1000 -- the literal was what let the
two drift apart.

The testdata wrapper concatenated args.ID straight into the request path,
contradicting PathSegment's own documented rule and the chat-brief
example. ValidateRequestView does not cover this: "abc/other-users-file"
cleans to itself, so an unescaped separator silently retargets the
request. Since testdata is what an integrator copies first, route both
call sites through one readRequest helper, mirroring chat-brief.

The e2e assertion could not have caught it either -- PathSegment("chat_1")
is "chat_1", so the check passed with or without the call. It now sends
"chat/1" and asserts %2F reaches the wire; removing PathSegment fails it.

* fix(command): deny network to the pre-confirmation hooks and four review findings

Normalize and Validate run before the high-risk confirmation gate, and
both received the full CommandContext, so a high-risk business command
could POST or DELETE from Validate and leave remote side effects behind
before the user was ever asked to confirm. Moving the gate earlier would
contradict the documented hook order and would also make --dry-run
require --yes. The design already forbids this from the other side --
Validate is specified as parameter checking that issues no request -- so
enforce that instead: Normalize and Validate get a context whose CallJSON
and CollectPages refuse, while PreflightScopes stays available. The guard
sits in CommandContext rather than in the wiring, so a future adapter
that wires the callbacks anyway still cannot reach the API. commandtest
mirrors it, otherwise a command would pass its tests and fail only in
production.

Page.Items now starts non-nil. It is declared required;nonnullable, but a
zero-item collection encoded as {"items":null}, which a caller generating
types from the published schema would reject.

NewCmdAuthWithRecovery and NewCmdSchemaWithVisibility are restored as
wrappers. Both were dropped for shortcut-aware variants, and both are
reachable from outside this module: CommandVisibility is an ordinary
exported func type, and *recovery.Projector cannot be named by an outside
caller but can be passed as nil. A signature test now pins them.

The path-traversal fixture said "../../secret", which the deterministic
gate rejects as a generic credential assignment -- the reason CI is
currently red. The filename carries no meaning; it is now "../../outside".

* test(cmd): exercise the retained constructors instead of naming them

The compatibility wrappers restored for outside callers are unreachable
from inside this repository by construction, so the incremental dead-code
gate rejected them. A signature-only assertion did not help: taking a
function value and discarding it leaves the body unreachable, and it
proved nothing about whether the wrapper still builds a working command.

Call each one and assert the command it returns. NewCmdAuthWithRecovery
is called with a nil projector, which is the exact call an outside module
can make and the reason the wrapper has to keep compiling.

Verified with the same deadcode version CI runs: neither function is
reported, and no other function in this branch's files is either.

* docs(command): document the hook contract and pin dry-run note idempotency

Hooks is the first type a business author reads and carried no field
documentation, so the rules lived only in the design doc: which of DryRun
and DryRunE to set, that setting both fails to compile, that Execute owns
the API call and must not write stdout, and that Normalize and Validate
run before the confirmation gate and therefore get no network.

The choice between DryRun and DryRunE is not old-versus-new -- neither is
legacy. It follows from whether building the preview can fail, which is
now what the field docs say.

Also pin the dry-run note as idempotent. convertDryRun writes the
bounded-repeat note into the projection it builds, never back into the
hook's *DryRun, and DryRunAPI.Desc assigns rather than appends, so a hook
that caches and returns the same preview cannot accumulate the note. Both
properties were true and neither was tested.

* refactor(command): settle the dry-run constructor, tips, and domain enum

Three narrowings of the V1 business-command contract, none of which has a
published compatibility surface: extension/command does not exist on main.

Preview and NewDryRun were the same constructor twice -- one empty, one
seeded with requests. Fold them into a variadic NewDryRun. Every existing
NewDryRun() call keeps compiling, and the domain word in the contract is
now spelled one way. The type DryRun already owns that identifier in this
package, so naming the constructor DryRun outright cannot compile.

Drop Metadata.Tips. It was pure passthrough into common.Shortcut.Tips and
nothing in the execution path read it, so business commands lose only the
ability to declare help tips; the repository's own typed shortcuts keep
theirs. The mount test asserted a tip reached the rendered help as proof
that metadata survives the extension -> commandhost -> common.Shortcut ->
help conversion; it now asserts the risk line, which travels the same path.

Hand-write the domain enumeration and delete the generator. Generating
from shortcuts.AllShortcuts silently omitted approval, attendance and
mindnotes: all three are published under `lark-cli --help` and served by
typed and raw API commands, they just own no shortcut. The enum is now
the 23 domains the CLI actually exposes.

Those three would otherwise have been constants that compile and always
fail, because CompileSets derived its mountable domains from the same
shortcut list. It now reads the service registry, and shortcuts/register.go
already creates a domain command group on demand when no built-in occupies
it, so a business command can mount under a shortcut-less domain.

* ci: stop generating extension/command

The domain enumeration is hand-written now and extension/command holds no
go:generate directive, so the path was a no-op that still read as if the
package carried generated files.

* refactor(command): drop DryRunE and let the preview render like a built-in

DryRunE has no counterpart in the shipped CLI: `git show
main:shortcuts/common/types.go` has no such field, it arrived with the
typed-shortcut framework this branch imported, and no shortcut in the
repository sets one. Business commands get the single DryRun hook that
built-in shortcuts have. Validate already runs before it and owns the
error channel, so a preview that cannot be built still fails there with a
typed error -- which is what the repointed tests now assert, end to end
through --dry-run and through commandtest.Preview.

Also stop appending the bounded-repeat note. convertDryRun added "with
--page-all, repeats with the returned page_token until exhaustion or
--page-limit" to every Page[T] preview, so an external command's dry-run
carried a sentence its author never wrote. Built-in paginated shortcuts
say this themselves when they want it (im_chat_members_list.go calls
dry.Desc), and external commands now do the same: the framework renders
the description it was given and nothing else.

The dry-run context keeps refusing requests -- runner.go does the same for
built-ins, so removing that would be the divergence, not the alignment.
Only the word changes: "offline" was our own vocabulary for what the rest
of the CLI calls dry-run.

* refactor(command): drop the partial-failure outcome from the contract

Business commands now return Success only. Partial, OutcomeDefinition,
PartialFailureDefinition and FailedItemDefinition leave the public surface
along with Execution.Partial and the host adapter's receipt conversion.

Result keeps its outcome field. It is no longer a choice -- Success is the
only value -- but it is also how the host tells a returned Result apart
from the zero value that accompanies an error, which is the check
commandtest.Execute makes before reporting "returned both Result and
error". Collapsing it to nothing would delete that signal.

The exemplar commands that returned Partial keep their scenarios: a
best-effort scope failure still marks every item failed and appends the
snapshot, and the multi-call audit still records the owner it could not
resolve. That information lives in the command's own Data (Items[].State
plus Failures), not in the outcome, so the tests assert the same facts and
only the outcome assertion is gone. The deep-copy test moved its nested
JSON exemplar from FailedValues to InputDefault.Value, keeping
cloneJSONValue covered.

shortcuts/common still defines PartialFailure for built-in typed
shortcuts. That is the imported framework, untouched here.

* refactor(shortcuts): walk pages once for built-in and external commands

Commit 4d0c6ea61 added internal/pagination, moved PaginateInto onto it,
and then wrote a second caller for externally declared commands. Both
assembled the same Walk options, cloned the same params, read the same
cursor and mapped the same walk error; only the policy source, the call
path and the accumulator ever differed.

Those three now parameterize one pageWalk. PaginateInto keeps calling
through the RuntimeContext and keeps its per-page progress line; external
commands keep CallTypedAPI, the walker's context and their undecoded
pages, which the public contract needs because it decodes them into its
own Page[T]. Behavior is unchanged on both sides -- the external walk
still leaves Wait nil, which internal/pagination fills with WaitContext,
so --page-delay works exactly as before.

pageWalk is deliberately generic-free so one struct serves both callers;
the typed half of the built-in path moved to addDecodedPage.

* refactor(shortcuts): make the external page walk PaginateInto's twin

CollectCommandPages now differs from PaginateInto only where the context
type forces it. It takes the same PageAccumulator, decodes each page into
T through the same addDecodedPage, returns the same *output.PaginationMeta
and reads the same state out of the same walk, in the same order.

What is left is what the interface cannot supply. An externally declared
command compiles in the business module, so it holds a CommandContext
rather than a *RuntimeContext: the context arrives as a parameter because
the interface carries none, the call goes through CallTypedAPI, and there
is no progress line because deciding to print one needs StderrIsTerminal,
JqExpr and Format, none of which the interface exposes.

The all parameter stays. It is the complete-set policy CollectAllPages
depends on -- collect to exhaustion under the hard page bound instead of
obeying --page-all and --page-limit -- and PaginateInto has no way to
express it, since resolvePaginationPolicy only ever reads flags. Dropping
it would quietly turn a command that must see the whole set into one a
user can truncate with --page-limit 1.

CommandPageCollection is gone with it: pages accumulate in commandhost's
own accumulator, the way every built-in shortcut already accumulates its
own. One consequence of sharing the decode: a page whose response carries
no data object is now an error on this path too, as it always was for
built-ins.

* fix(shortcuts): reject recursive Data and Args types during compilation

shapeForType and compileStructShape called each other without recording the
Go types already being walked, so a self-referential type recursed forever.
The walk ran during command registration and ended in a stack overflow --
a fatal runtime error rather than a panic, so no recover boundary could
contain it and one extension command took the whole CLI down before --help,
schema, or any unrelated command could run. That also broke
CompileErasedDefinition's documented promise to compile without panic.

Thread the struct types open on the current recursion path through both
functions and return a compile error on a repeat visit, pointing at the
explicit Shape escape hatch. Membership is scoped to the path, not the whole
walk, so a type reused as a sibling or at another depth stays legal.

Covers self-reference through a slice and through a pointer, mutual
recursion through two types, the JSON-encoded Args path, and the public
CompileErasedDefinition contract.

* fix(command): share one result protocol between the host and commandtest

commandtest.Execute only checked that Data carried the expected type. Generic
erasure leaves a correctly typed zero Data behind, so a business command that
returned Result{} instead of Success(data) passed the type assertion and the
test reported success. Production rejects the same result, which left
extension authors with green tests and a command that failed on every real
invocation -- exactly the guarantee commandtest exists to provide.

Add ValidateHostResult in extension/command and call it from both
commandtest.Execute and the host adapter's execute hook, so the two surfaces
cannot drift. It rejects an empty or unsupported outcome and mirrors the
pagination receipt checks the host already applies: declared Page output,
pages of at least one, non-negative items, and next-token state consistent
with completeness.

RunWithFlags is covered because it delegates to Execute.

* feat(command): expose reusable download capabilities

* test(command): make the chat-brief example testable and cover its hooks

The example declared both commands as inline Definition literals handed
straight to Define. Define erases the type parameters and returns an opaque
Command that cannot hand its Definition back, while commandtest.Execute takes
the Definition -- so the shape the example demonstrated could not be unit
tested at all. Neither shipped example had a test file, so nobody had walked
the copy-the-example-then-add-a-test path.

Lift both declarations into Definition-returning functions, the shape the
repository's own commandtest suites already use, and keep the compiled
Commands as package vars so main is unchanged. The configuration bodies are
untouched.

Add the tests that shape exists for: the single-read projection and its
Validate rejection, plus the Page[T] contract for default single-page reads
and a --page-all walk through RunWithFlags. All four run offline through the
commandtest recorder.

* test(commandtest): cover the ordered URL download script

Recorder.ReplyURL shipped without a caller, so the incremental dead code
gate flagged it as new unreachable code and blocked the branch. The existing
URL download test uses the unordered RespondFile constructor, which never
exercises the URL assertion ReplyURL exists for.

Mirror the ReplyJSON pair: one test walks two scripted URLs in order and
checks the recorded source URLs, content types, and artifacts; the other
points DownloadURL at an unscripted URL and expects the mismatch error.

* feat(command): accept @file input for external commands

V1 rejected the file value source, leaving external commands with inline
flags and stdin only. A process has one stdin, so a command whose body is
too large or too quoted for the shell -- an XML document update is the case
that surfaced this -- had no second way to receive it, and the caller had to
fall back to shell escaping.

Nothing downstream was missing: resolveInputFlags already resolves @path and
the @@ escape through the invocation's FileIO, help renders the "@file"
affordance, and legacyInputSources maps the source onto the compiled flag.
The gap was the public constant and the host allow-list.

Export SourceFile and let it through compilation. Unknown sources still fail
the same way, which the rewritten host test now pins alongside the compiled
flag actually carrying both extra sources -- silently dropping a declared
source is the regression worth catching.

Give the wrapper fixture a command whose content flag declares all three
sources, and drive it end to end: @file and stdin both reach the request
body, and the help text advertises them.

* revert(content): drop the default content exports from the extension surface

Exporting the repository's embedded skills and affordance trees turned two
content directories into Go packages, because go:embed cannot reach up out of
a package directory and the repository root is package main. That cost two
things: a .go file living inside an authored-content tree, where a future
content type is silently omitted until someone edits the glob, and an embed
directive per tree where the root previously covered both in one.

The need it served does not exist. The distribution driving this work ships
its own skills and does not consume the official set, and a wrapper that does
want them can supply a tree through cmd.SetEmbeddedSkillContent, which is what
extension/platform already documents.

Restore content_embed.go, the SkillsOverlay comments, and the platform README
to their main state, and delete the two exporting packages. The example and
fixture wrappers now ship no embedded content, which is what a wrapper that
does not compile the repository root actually gets; the e2e assertion that
depended on inheriting lark-doc goes with it.

* fix(command): close the review findings on API surface and storage commit

Five findings from the extension-v1 review, each verified by a test that fails
against the previous implementation.

Source compatibility of auth.LoginOptions. The shortcut snapshot was an
unexported field on a struct that appears in the exported runF signature, which
ends positional literals for every caller outside this module. It becomes a
closure capture plus an explicit authLoginRun parameter, and the six domain
helpers collapse into domainResolver methods, removing five xxxWithShortcuts
twins that only tests reached. common.Shortcut.DryRunE goes too: it was a new
exported field with no production caller. The unexported typed field stays, so
Shortcut itself is still not positional-literal compatible -- that is a
deliberate remaining gap, since relocating it would need a global mutable map or
a wider signature change.

One canonical wire projection. queryValues stringified and dropped nils for the
live call while the dry-run preview and the pagination walk forwarded raw values,
so a preview could describe a request the runtime would never send. canonicalQuery
is now the only projection and all three consumers derive from it. Dry-run output
for numeric parameters therefore reads "20" instead of 20, matching what the
query string actually carries.

No-clobber as a storage guarantee. IfExistsFail checked existence, downloaded,
then committed with a rename that replaces unconditionally, so a target created
during the transfer was silently overwritten. The commit step is now an optional
ExclusiveFileIO capability: content lands in a temp file and is published with
Link, which refuses an existing target and never exposes a partial file. A
provider without the capability is refused rather than served a guarantee it
cannot keep.

V1 public surface. Removes NewDomain and its options (host compilation rejected
them), HostDomain.IsNew, reservedRootNames, and the unproducible
ResultMetaDefinition.Count; narrows Hooks.Renderers to a single PrettyRenderer,
since pretty was the only key the compiler accepted; and demotes the generic
authoring layer in shortcuts/common to unexported, as no production code outside
that package used it.

commandtest runs the production compiler. Execute, RunWithFlags and Preview now
share compileForTest, so a wrong tag, Shape or relation fails in the unit test
instead of at CLI startup. Applying this surfaced two long-standing contract
violations in the package's own fixture.

* refactor(command): keep a single authoring contract

* revert(schema): keep the schema command blind to shortcuts

The schema command serves the generated API catalog only, matching main.
Drop the shortcut contract lookup and completion from cmd/schema and
restore the constructor surface. ShortcutSchema stays on the sealed
commandbridge surface, where the host compiler tests assert the contract;
the CLI itself no longer consumes it. The surface scenario and wrapper
e2e pin the boundary from the other side: schema resolution and
completion must not see mounted shortcuts.

---------

Co-authored-by: sang-neo03 <266690410+sang-neo03@users.noreply.github.com>
Co-authored-by: liangshuo-1 <266696938+liangshuo-1@users.noreply.github.com>
2026-08-25 20:22:13 +08:00
guokexin.02 de45ec61e6 fix: honor recovered E2E retries (#2400) 2026-08-19 15:31:05 +08:00
guokexin.02 115357d5c5 ci: verify release Go toolchain (#2256) 2026-08-11 18:24:17 +08:00
Yuxuan Zhao 8b50caead6 ci: allow concurrent live e2e jobs (#2182) 2026-08-04 18:56:14 +08:00
guokexin.02 c828372f99 ci: notarize macOS release candidates (#2073) 2026-08-04 13:19:54 +08:00
liangshuo-1 3d2c10cd0b fix(ci): validate static workflow identity (#2015) 2026-07-27 19:39:11 +08:00
guokexin.02 4eefe32c1a ci: harden npm release publishing (#1918) 2026-07-22 20:53:43 +08:00
luozhixiong01 0b6faa01bf ci: deduplicate PR runs and serialize live E2E (#1888)
* ci: deduplicate PR runs and serialize live E2E

* ci: preserve live E2E cleanup on supersession

* ci: harden live E2E supersession check

* ci: gate live E2E on dry-run planning

Make the dry-run result a hard prerequisite for live E2E so skip-mode changes never acquire the repository-wide slot. This intentionally trades one full dry-run duration of live startup latency for lower contention on the exclusive queue.

* ci: bound dry-run E2E planning

The dry-run job is now a hard prerequisite for live E2E. Bound its
execution so a stalled planning job cannot delay a PR verdict for the
default six-hour job limit.

* test: tighten live E2E supersession contract
2026-07-17 19:37:48 +08:00
Yuxuan Zhao 76ebd49382 test: stabilize live e2e auth retries (#1904)
* test: stabilize live e2e auth retries

* fix(e2e): scope shared tenant credentials
2026-07-17 17:20:45 +08:00
zhaojunlin0405 a6bc81596a ci: add L4 plugin-integration and sidecar-integration CI jobs (#1840) 2026-07-16 13:47:11 +08:00
evandance 4847f06ca8 fix: route brand-sensitive endpoints through the resolver (#1836) 2026-07-11 20:02:06 +08:00
Yuxuan Zhao 6f95c5eb22 e2e: harden CLI E2E retry, cleanup, and domain selection (#1709) 2026-07-07 19:41:11 +08:00
hanshaoshuai cf93ee051c feat(ci): add public content safeguards 2026-06-25 19:03:14 +08:00
hanshaoshuai 036e5799d3 fix(ci): bind semantic review to workflow run head 2026-06-23 18:21:29 +08:00
hanshaoshuai c61acb5264 feat: add ci quality gate 2026-06-17 16:29:33 +08:00
evandance 99e314fe0b feat(errs): typed envelope contract for auth-domain errors (#1135)
Every failure on the authentication, authorization, and configuration
path now surfaces as a typed structured error instead of an ad-hoc
envelope. Users and scripts that consume CLI output get:

  - a fixed nine-category taxonomy on the wire, each mapped to a
    stable shell exit code (authentication/authorization/config = 3,
    network = 4, internal = 5, policy = 6, confirmation = 10)
  - identity-aware detail fields (missing_scopes, requested_scopes,
    granted_scopes, console_url, log_id, retryable, hint) carried
    uniformly on the envelope
  - a single canonical policy envelope at exit 6; the legacy
    auth_error carve-out is retired
  - per-subtype canonical message + hint that preserves Lark's
    diagnostic phrasing and routes recovery to the right actor:
    app developer (app_scope_not_applied), user (missing_scope,
    token_scope_insufficient, user_unauthorized), or tenant admin
    (app_unavailable, app_disabled)
  - wrong app credentials classify as config/invalid_client whether
    surfaced by the Open API endpoint (99991543) or the tenant
    access-token mint endpoint (10003 / 10014), instead of
    collapsing to a transport error or api/unknown
  - local shortcut scope preflight emits the same
    authorization/missing_scope envelope (identity + deterministic
    missing-scope set) used by the post-call permission path, so AI
    consumers read the same structured shape from precheck and from
    server-returned permission denial
  - streaming download/upload failures keep the same network subtype
    split (timeout / TLS / DNS / transport) as the non-stream path
    instead of collapsing every cause to a generic transport failure
  - console_url is carried only on the bot-perspective
    app_scope_not_applied envelope (where the recovery action is
    "developer applies the scope at the developer console"); the
    user-perspective missing_scope envelope drops the field, since
    the only actionable user recovery is `lark-cli auth login --scope`
    and pointing an end user at a console they cannot modify is
    misleading
  - bind workflows (Hermes / OpenClaw / lark-channel) flatten dynamic
    Type tags to wire 'config' with the original module name kept
    as a metric label

All 10 typed errors are cause-bearing, nil-safe on .Error() and
.Unwrap(), and defensively clone slice setter inputs. Four lint
rules (CheckNilSafeError / CheckBuilderImmutable / CheckUnwrapSymmetry
/ CheckBuildAPIErrorArms) lock these invariants on migrated paths.
2026-05-30 19:08:41 +08:00
sang-neo03 33c292c05e feat(extension): Plugin / Hook framework with command pruning (#910)
* feat(extension): introduce Plugin / Hook framework with command pruning

Add a single public extension contract under extension/platform: integrators
implement the Plugin interface and register Observers, Wrappers, Lifecycle
handlers, and pruning Rules through the Registrar in one Install call.

Command pruning:
  - Rule (Allow / Deny / MaxRisk / Identities) with doublestar globs
  - 4-axis AND evaluation, parent-group aggregation, unknown-risk allow
  - Sources: Plugin.Restrict (single-rule) and ~/.lark-cli/policy.yml
  - Plugin path is fail-closed (envelope on rule error / multiple Restrict);
    yaml path is fail-open (warning, CLI continues)
  - strict-mode stubs now also write the denial annotation so the hook
    layer's denial guard physically isolates Wrap chains on them
  - HOME path never leaked through policy_source label

Hook framework:
  - Observer (panic-safe, Before/After), Wrapper (middleware, may short-circuit
    via AbortError), Lifecycle (Startup + Shutdown only)
  - Recover guards every plugin entry point: Capabilities(), Install(),
    Wrapper factory composition AND inner Handler, Lifecycle handlers
  - namespacedWrap copies AbortError so a plugin's package-level sentinel
    is never mutated across concurrent invocations
  - Selector unknown-risk uniform: ByExactRisk / ByWrite / ByReadOnly never
    match unannotated commands; safety-side hooks opt in via
    ByWrite().Or(ByUnknownRisk())

Bootstrap orchestration (cmd/build.go + cmd/policy.go):
  - InstallAll uses a staging Registrar + atomic commit
  - FailClosed plugin install / Plugin.Restrict conflict / Startup handler
    failure each install a structured envelope guard at every dispatch path
  - walkGuard neutralises every cobra bypass we know of (PersistentPreRunE
    first-wins, ValidateArgs, ParseFlags, legacyArgs, __complete /
    __completeNoDesc, non-runnable groups, required-arg subcommands)
  - cmd/root.go::Execute calls hook.Emit(Shutdown, runErr) after
    rootCmd.Execute; isCompletionCommand skips both __complete and
    __completeNoDesc so Tab completion never triggers Shutdown handlers

Capabilities consistency:
  - Restricts=true must declare FailurePolicy=FailClosed
  - RequiredCLIVersion (semver constraint) is validated against build.Version;
    a malformed constraint is treated as untrusted-config and aborts
    unconditionally, regardless of FailurePolicy (DEV builds included)

JSON envelope contract:
  - error.type closed enum: pruning / strict_mode / hook / plugin_install /
    plugin_conflict / plugin_lifecycle
  - reason_code closed enums per type, all referenced by structured tests

Bootstrap surfaces (new user commands):
  - lark-cli config policy show     -- JSON view of the active Rule + source
  - lark-cli config policy validate -- parse + schema + glob check, no apply

Coverage:
  - extension/platform: every public type has a unit test
  - internal/{pruning,hook,platformhost,policydecision,cmdmeta}: full coverage
    of denial guard isolation, AbortError sentinel safety, observer panic
    safety, lifecycle error/panic typing, staging atomic rollback
  - cmd/plugin_integration_test.go: end-to-end through buildInternal with
    synthetic and real command trees
  - cmd/install_guard_test.go: walkGuard covers auth / config / __complete /
    __completeNoDesc / non-runnable parents

* fix(pruning): deny stub must override Args + PersistentPreRunE

The pruning denyStub and the strict-mode stub previously only swapped
RunE plus Hidden + DisableFlagParsing. Cobra's dispatch order means
several pre-RunE gates can fire BEFORE the stub's RunE ever runs:

  1. Args validator: shortcut commands often declare cobra.NoArgs.
     With DisableFlagParsing=true the user's `--doc xxx --mode append`
     looks like positional args, so ValidateArgs surfaces a usage
     error instead of the pruning / strict_mode envelope. Observer
     hooks also miss the dispatch entirely.

  2. Parent PersistentPreRunE: cmd/auth/auth.go declares a
     PersistentPreRunE that returns external_provider when env
     credentials are set. Cobra's "first PersistentPreRunE wins
     walking up from the leaf" then short-circuits with
     external_provider instead of the leaf's denial envelope.

Both stubs now also set:

  - Args               = cobra.ArbitraryArgs   (bypass gate 1)
  - PersistentPreRunE  = no-op leaf hook       (bypass gate 2)
  - PreRunE / PreRun / PersistentPreRun = nil  (defensive)

Effect: dispatch reaches the wrapped RunE, observers fire, the real
pruning / strict_mode envelope is emitted regardless of credential
provider or flag count.

Adds regression tests covering both gates on both stub paths.

* fix(config): policy subcommand bypasses parent's credential check

cmd/config/config.go::NewCmdConfig declares a PersistentPreRunE that
calls f.RequireBuiltinCredentialProvider; with env credentials set,
it returns external_provider for every config subcommand.

`config policy show` and `config policy validate` are READ-ONLY
diagnostic commands -- they inspect or parse the user-layer rule
without touching credentials. They MUST work regardless of which
credential provider is active, otherwise users on env-credential
deployments cannot debug their policy.

Same shape as the codex C11/C13 fix: install a no-op leaf-level
PersistentPreRunE on the `policy` group so cobra's "first walking up
from leaf" rule picks ours over the config parent's.

Regression caught by divergent e2e (F1-F6 all returned external_provider
before this fix; all pass after). Adds a unit test pinning the
PersistentPreRunE override.

* feat(shortcuts): tag service groups with cmdmeta.Domain

RegisterShortcutsWithContext now calls cmdmeta.SetDomain on each
service-level cobra.Command (im, docs, drive, calendar, ...) so the
business-domain axis is actually populated on every shortcut leaf via
parent-chain inheritance.

Before this change, platform.ByDomain("docs") never matched any
command: the domain annotation was unset across the entire shortcut
tree, so the selector's d != "" guard always failed and risk-style
selectors silently degraded to no-op.

The SetDomain call is placed AFTER the create-or-reuse branch so it
fires whether the service command was freshly created here or had
already been added by cmd/service/service.go's OpenAPI auto-
registration (which runs first and creates im, drive, calendar, etc.).
Without this placement only pure-shortcut services like docs would
have been tagged.

Adds a regression test asserting:
  - service-group cobra.Command carries the cmdmeta.domain annotation
  - leaf shortcuts inherit the domain via parent-chain walk

* feat(diagnostic): add unconditionally allowed command paths for introspection

* feat(plugins): add diagnostic command to inspect installed plugins and their contributions

* fix(cli): surface unknown_subcommand error instead of silent help fallback

When a user passed an unknown subcommand or shortcut (e.g. `lark-cli drive
+bogus`), cobra returned `flag.ErrHelp` for the non-runnable group command,
printed the parent help, and exited 0. AI agents couldn't distinguish a
typo from an intentional help request.

Install a tree-wide guard that attaches a RunE to every group command
without its own Run/RunE. The RunE forwards no-args invocations to help
(preserving prior behavior) and emits a structured unknown_subcommand
ExitError (exit 2) listing available subcommands when args are present.

* refactor(envelope): rename error.type pruning/strict_mode to command_denied

The envelope's `type` field was leaking implementation terms ("pruning",
"strict_mode") that describe enforcement mechanism rather than the user-
facing semantic. It also duplicated `detail.layer`, and forced consumers
to branch on two values for the same conceptual error ("a command was
denied by policy").

Collapse both into a single semantic type "command_denied". The
enforcement layer ("pruning" / "strict_mode") is preserved in
`detail.layer` so debugging and per-layer diagnostics still work.

* feat(platform): fail closed on unannotated/invalid risk when a Rule is active

The pruning engine used to treat any command without a risk annotation as
ALLOW even when a Rule with MaxRisk was set, and would silently skip the
MaxRisk comparison whenever the command's risk string was outside the
closed taxonomy. Both gaps let an unannotated or typo'd write command
slip past an "agent read-only" pruning rule.

Engine now denies before any other axis when a Rule is registered:
  - reason_code "risk_not_annotated" for commands with no risk
  - reason_code "risk_invalid"        for commands whose risk is outside
                                      the read | write | high-risk-write
                                      taxonomy (e.g. typo "wrtie")

Main-flow is preserved: a nil Rule still returns Allowed=true
unconditionally, so a CLI with no pruning plugin behaves identically to
before. ByUnknownRisk() is removed from the public surface since the
Unknown state is no longer reachable through risk-based selectors when
any Rule is active; safety-side widening composition is no longer needed.

* chore(config): hide diagnostic policy/plugins commands from --help

`config policy show`, `config policy validate`, and `config plugins show`
are local-introspection-only commands kept behind the pruning
diagnostic whitelist so operators can always inspect why a command was
denied. They do not need to surface in `--help` for AI agents and were
contributing to help noise.

Hide the `policy` and `plugins` parent groups and both `show` /
`validate` leaves. Commands remain callable by exact name and continue
to bypass user-layer pruning via diagnosticPaths.

* style: gofmt

* fix(platform): nil Selector honours None contract; reject multi-doc policy yaml

- selector.go: And/Or/Not now treat nil Selector as None() per godoc,
  preventing runtime panic when composed selectors are invoked.
- schema.go: Parse rejects multi-document YAML input so a stray '---'
  separator can't silently drop trailing policy constraints.

* chore: go mod tidy

* feat(extension/platform): plugin SDK with policy engine, hooks, and Builder

Introduces extension/platform — the in-process plugin SDK external
Go forks of lark-cli use to extend or restrict the command surface.
Plugins compile in via blank import; there is no dynamic loading
and no RPC isolation.

Public SDK (extension/platform):

  - Plugin interface (Name / Version / Capabilities / Install).
  - Registrar verbs: Observe, Wrap, On, Restrict.
  - Hook types: Observer (side-effect, panic-safe, fires Before/After
    RunE), Wrapper (middleware, may short-circuit via AbortError),
    LifecycleHandler (Startup / Shutdown), Selector with nil-safe
    And/Or/Not composition.
  - Risk / Identity are defined string types with closed taxonomies;
    ParseRisk / ParseIdentity convert raw strings with the
    absent-vs-invalid distinction the engine relies on.
  - Builder ergonomic constructor (NewPlugin().Observer().Wrap()
    ...MustBuild()) that enforces name/hookName grammar, hookName
    uniqueness, and the Restrict ↔ FailClosed pairing regardless of
    call order.
  - Invocation is a read-only interface; the framework's concrete
    invocation type lives in internal/hook so plugins cannot
    fabricate denial / strict-mode / identity state. Args() returns
    a defensive copy on every call so hook mutation cannot leak
    into the original RunE.
  - CommandDeniedError + AbortError carry structured fields for the
    closed `command_denied` / `hook` envelope contract.
  - ResetForTesting gated behind //go:build testing.
  - README + godoc examples (Observer / Wrapper / Restrict) + two
    runnable example forks (audit-observer, readonly-policy).

Host (internal/platform, internal/hook, internal/cmdpolicy):

  - InstallAll: staged plugin registration with atomic commit, panic
    isolation, FailOpen / FailClosed semantics, RequiredCLIVersion
    semver check, single-Restrict invariant, duplicate-plugin-name
    detection.
  - hook.Install wraps every runnable cmd.RunE with:
    Before observers (panic-safe) → denial guard → composed Wrap
    chain → original RunE → After observers (always fire, even on
    err). Denied commands physically bypass the Wrap chain so a
    plugin Wrapper cannot suppress or rewrite a denial; observers
    still see the attempt for audit.
  - Recover shim around plugin Wrappers converts panics (including
    the factory call) into a structured `hook` envelope with
    reason_code=panic; namespacing shim attributes AbortError to
    the namespaced hook name.
  - cmdpolicy (renamed from internal/pruning) is the user-layer
    command policy engine: walks the cobra tree, evaluates each
    runnable command against a Rule's four-axis filter (Allow /
    Deny / MaxRisk / Identities), produces parent-group aggregate
    denials, and installs denyStubs. Rule.AllowUnannotated opts out
    of the unannotated-deny gate for gradual adoption; risk_invalid
    typos always deny with an edit-distance "did you mean"
    suggestion.
  - Strict-mode stub in cmd/prune.go composes the shared
    detail.* / wrapped CommandDeniedError shape via cmdpolicy
    helpers (BuildDenialError / CommandDeniedFromDenial /
    DenialDetailMap), so command_denied envelopes from strict-mode
    and user-layer policy carry the same closed-enum fields
    (detail.layer / reason_code / policy_source). The historical
    short Message + independent Hint are preserved unchanged.
  - cmdpolicy/yaml: structural parsing of ~/.lark-cli/policy.yml
    with KnownFields strict mode, including allow_unannotated.
  - `config policy show` / `config policy validate` and the plugin
    inventory diagnostic surface the resolved Rule (allow,
    deny, max_risk, identities, allow_unannotated) and the hook
    contributions per plugin.

Envelope contract (docs/extension/reason-codes.md):

  - error.type is a closed set: command_denied, hook, plugin_install,
    plugin_conflict, plugin_lifecycle.
  - reason_code is a closed enum per error.type, dispatched on by
    external agents and CI integrations.
  - detail.layer = "policy" | "strict_mode" attributes the rejection.

Build / CI:

  - Makefile unit-test / vet / coverage and ci.yml fast-gate +
    unit-test + coverage now pass -tags testing so register_testing.go
    is visible; ./extension/... is in the package list so the SDK's
    own tests actually run.
  - fmt-check and examples-build Makefile targets.
  - bmatcuk/doublestar/v4 added as a direct dependency for `**` glob
    matching in Rule.Allow / Rule.Deny.

Author-facing material:

  - docs/extension/ (quickstart, plugin-author-guide, reason-codes)
    is provided in the working tree but kept out of git tracking
    per repo convention (.gitignore covers docs/).

Change-Id: I3b8ecc2923bd54c2dff19e5dce8a0855a6f9e703

* feat(extension/platform): plugin SDK with policy engine, hooks, and Builder

Introduces extension/platform — the in-process plugin SDK external
Go forks of lark-cli use to extend or restrict the command surface.
Plugins compile in via blank import; there is no dynamic loading
and no RPC isolation.

Public SDK (extension/platform):

  - Plugin interface (Name / Version / Capabilities / Install).
  - Registrar verbs: Observe, Wrap, On, Restrict.
  - Hook types: Observer (side-effect, panic-safe, fires Before/After
    RunE), Wrapper (middleware, may short-circuit via AbortError),
    LifecycleHandler (Startup / Shutdown), Selector with nil-safe
    And/Or/Not composition.
  - Risk / Identity are defined string types with closed taxonomies;
    ParseRisk / ParseIdentity convert raw strings with the
    absent-vs-invalid distinction the engine relies on.
  - Builder ergonomic constructor (NewPlugin().Observer().Wrap()
    ...MustBuild()) that enforces name/hookName grammar, hookName
    uniqueness, and the Restrict ↔ FailClosed pairing regardless of
    call order.
  - Invocation is a read-only interface; the framework's concrete
    invocation type lives in internal/hook so plugins cannot
    fabricate denial / strict-mode / identity state. Args() returns
    a defensive copy on every call so hook mutation cannot leak
    into the original RunE.
  - CommandDeniedError + AbortError carry structured fields for the
    closed `command_denied` / `hook` envelope contract.
  - ResetForTesting gated behind //go:build testing.
  - README + godoc examples (Observer / Wrapper / Restrict) + two
    runnable example forks (audit-observer, readonly-policy).

Host (internal/platform, internal/hook, internal/cmdpolicy):

  - InstallAll: staged plugin registration with atomic commit, panic
    isolation, FailOpen / FailClosed semantics, RequiredCLIVersion
    semver check, single-Restrict invariant, duplicate-plugin-name
    detection.
  - hook.Install wraps every runnable cmd.RunE with:
    Before observers (panic-safe) → denial guard → composed Wrap
    chain → original RunE → After observers (always fire, even on
    err). Denied commands physically bypass the Wrap chain so a
    plugin Wrapper cannot suppress or rewrite a denial; observers
    still see the attempt for audit.
  - Recover shim around plugin Wrappers converts panics (including
    the factory call) into a structured `hook` envelope with
    reason_code=panic; namespacing shim attributes AbortError to
    the namespaced hook name.
  - cmdpolicy (renamed from internal/pruning) is the user-layer
    command policy engine: walks the cobra tree, evaluates each
    runnable command against a Rule's four-axis filter (Allow /
    Deny / MaxRisk / Identities), produces parent-group aggregate
    denials, and installs denyStubs. Rule.AllowUnannotated opts out
    of the unannotated-deny gate for gradual adoption; risk_invalid
    typos always deny with an edit-distance "did you mean"
    suggestion.
  - Strict-mode stub in cmd/prune.go composes the shared
    detail.* / wrapped CommandDeniedError shape via cmdpolicy
    helpers (BuildDenialError / CommandDeniedFromDenial /
    DenialDetailMap), so command_denied envelopes from strict-mode
    and user-layer policy carry the same closed-enum fields
    (detail.layer / reason_code / policy_source). The historical
    short Message + independent Hint are preserved unchanged.
  - cmdpolicy/yaml: structural parsing of ~/.lark-cli/policy.yml
    with KnownFields strict mode, including allow_unannotated.
  - `config policy show` / `config policy validate` and the plugin
    inventory diagnostic surface the resolved Rule (allow,
    deny, max_risk, identities, allow_unannotated) and the hook
    contributions per plugin.

Envelope contract (docs/extension/reason-codes.md):

  - error.type is a closed set: command_denied, hook, plugin_install,
    plugin_conflict, plugin_lifecycle.
  - reason_code is a closed enum per error.type, dispatched on by
    external agents and CI integrations.
  - detail.layer = "policy" | "strict_mode" attributes the rejection.

Build / CI:

  - Makefile unit-test / vet / coverage and ci.yml fast-gate +
    unit-test + coverage now pass -tags testing so register_testing.go
    is visible; ./extension/... is in the package list so the SDK's
    own tests actually run.
  - fmt-check and examples-build Makefile targets.
  - bmatcuk/doublestar/v4 added as a direct dependency for `**` glob
    matching in Rule.Allow / Rule.Deny.

Author-facing material:

  - docs/extension/ (quickstart, plugin-author-guide, reason-codes)
    is provided in the working tree but kept out of git tracking
    per repo convention (.gitignore covers docs/).

Change-Id: I3b8ecc2923bd54c2dff19e5dce8a0855a6f9e703

* refactor(policy): remove validate command and update diagnostics

* fix(extension/platform): address PR review must-fix items

- cmdpolicy: skip AnnotationPureGroup commands in EvaluateAll,
  aggregateParents, and hasRunnableDescendant so user-layer policy
  no longer blocks `<group> --help` after the unknown-subcommand
  guard attaches RunE to every parent
- cmd/root: tag guarded parent groups with AnnotationPureGroup
- extension/platform: drop `//go:build testing` from register_testing.go
  so `go test ./...` works without an extra build tag
- extension/platform/README: inline reason_code reference, fix plugin
  lifecycle diagram order (init/Register precede RegisteredPlugins)
- cmd/platform_bootstrap: route userPolicyPath through
  core.GetBaseConfigDir so LARKSUITE_CLI_CONFIG_DIR is honoured
- cmdpolicy: add RedactHomeDir helper, fold base config dir and
  $HOME prefixes for config policy show + resolver errors
- internal/platform: reject unrecognised FailurePolicy values with
  invalid_capability instead of silently fail-open
- cmd/config: surface diagnostic policy/plugins commands in
  `config --help` Long text
- CHANGELOG: document command_denied error.type rename and
  unknown_subcommand exit-2 behavior change

* fix(extension/platform): address CodeRabbit review comments + CI gofmt

- hook/install: propagate wrapper-injected ctx to invokeOriginal so
  RunE/Run see context values added by upstream Wrappers
- hook/testing: SetStderrForTesting returns a restore func; tests now
  defer it via t.Cleanup to avoid cross-test sink leakage
- cmdpolicy/active: deep-copy ActivePolicy.Rule on SetActive/GetActive
  so callers can't mutate the stored global through shared slices
- platform/inventory: deep-copy Inventory + nested Plugins / HookEntry
  / RuleView slices on SetActiveInventory / GetActiveInventory
- platform/staging: Restrict clones the plugin-supplied Rule before
  retaining it so the plugin can't mutate it after Install returns
- platform/version: reject RequiredCLIVersion with more than three
  numeric components instead of silently truncating 1.2.3.4 to 1.2.3
- cmd/platform_bootstrap: clear cmdpolicy.SetActive on yaml resolver
  error so config policy show doesn't surface a stale rule
- cmd/platform_bootstrap_test: tmpHome pins LARKSUITE_CLI_CONFIG_DIR
  so host env can't bleed into the policy test fixtures
- cmdpolicy/apply: installDenyStub returns bool; Apply count no longer
  over-reports when strict-mode short-circuits the install
- cmdpolicy/engine: aggregateParents now returns the runnable hybrid's
  own denial status when all children are placeholder branches
- cmdpolicy/resolver_test: use t.TempDir()-rooted missing path instead
  of hardcoded /nonexistent for hermetic missing-file assertion
- cmd/config/plugins: empty-inventory branch emits total: 0 so the
  JSON schema stays stable across populated/empty cases
- cmd/platform_guards_test: select leaf by RunE != nil (not Runnable)
  so the test doesn't nil-deref on Run-only commands
- gofmt run on previously committed cmdpolicy/path*.go (CI fast-gate)

* fix(cmdpolicy): replace filepath.Abs with filepath.Clean for lint policy

The depguard / forbidigo rule blocks filepath.Abs in internal/ on the
grounds that it accesses the filesystem (Getwd) directly. Switch
RedactHomeDir + foldPrefix to operate on filepath.Clean strings; real
callers pass already-absolute paths (resolver builds yamlPath via
filepath.Join on the absolute config root), so the redaction outcome
is unchanged for production inputs. Relative inputs fall through to
the unchanged branch — filepath.Rel rejects the mixed-absoluteness
case with an error, which the foldPrefix helper already treats as
"not a hit".

* refactor(cmdpolicy): pure Resolve + drop path redaction & verbose comments

- Resolve becomes a pure function; I/O moves to LoadYAMLPolicy so
  precedence selection can be unit-tested without vfs mocks
- ActivePolicy drops YAMLPath; config policy show JSON loses yaml_path
  and yaml_shadowed (and the TOCTOU stat that surfaced them)
- RedactHomeDir and path_test.go removed: the home-dir folding was only
  earning its keep through the now-deleted yaml_path field
- cmd/build.go bootstrap block trimmed from 71 to 39 lines by cutting
  PR-rationale comments; one note kept for the fail-CLOSED-vs-fail-OPEN
  business rule
- cmd/config/config.go: parent Long no longer hard-codes hidden command
  hints, matching their Hidden:true intent

Change-Id: Icfbb818ce3ef523c63286bfbed34c49be08ed6a2

* refactor(platform): drop StrictMode/Identity from Invocation interface

These two accessors were documented in the public SDK as "After observers
always see ok=true" but the framework never plumbed values to them, so they
always returned ("", false). Zero internal/example/test callers; a plugin
author trusting the doc would silently get wrong behaviour.

Identity is also fundamentally unsuited for Before observers (per-command
identity resolves inside RunE via f.AuthFor, after Before fires). StrictMode
is a global value better placed on a Framework/Environment interface than
per-Invocation. Removing is non-breaking now (no callers); adding later is
non-breaking too.

Change-Id: Ice200543e9bca3bda759ad98a6e34a56df69e915

* fix(prune): preserve original metadata on strict-mode denial stubs

strictModeStubFrom built a fresh *cobra.Command from scratch, dropping
the original command's annotations (risk_level, lark:supportedIdentities,
cmdmeta.domain) and help text. cobraCommandView is a live proxy walking
parent annotations, so after the Remove+Add replacement, audit observers
firing on a strict-mode-denied command saw Cmd().Risk()=("",false) and
Cmd().Identities()=nil -- breaking the first-class use case for
audit/compliance plugins.

Copy child.Annotations into the stub (stamping the denial annotations on
top) and propagate Short/Long for help-text parity with
cmdpolicy/apply.go::installDenyStub, which preserves these by virtue of
mutating in place.

Regression test asserts risk_level / supportedIdentities / Short / Long
all survive replacement, alongside the denial annotations.

Change-Id: I19810a34575996344b63e839066888c154d69335

* chore(platform): align docs with implementation; fold home in yaml warnings

Followup cleanup to the previous three refactor commits, addressing review
fallout where public docs / examples / contract notes still pointed at
deleted symbols or unimplemented designs:

- cmd/build.go: Build() docstring now mentions the plugin install + Startup
  emit side effects; Shutdown only fires on Execute path
- extension/platform/doc.go, lifecycle.go, invocation.go: drop references
  to the deleted StrictMode/Identity methods, restore minimal Godoc on
  Cmd/Args/Started
- extension/platform/view.go, cmd/platform_bootstrap.go,
  internal/hook/install.go: rewrite "snapshot before pruning" promise to
  match the actual contract (live view + strict-mode stub metadata
  preservation)
- cmd/platform_guards_test.go: stubInvocation drops the two old methods
- cmd/platform_bootstrap.go: redactHome() last-mile folds $HOME -> ~ in
  warnPolicyError so an os.PathError carrying the absolute policy path
  does not leak the user's home dir to stderr / agent / CI logs
- examples/readonly-policy/README.md: drop yaml_path from the sample
  `config policy show` envelope (the field was removed in 52cbb92)

Change-Id: I2874cc2cf9225dfa44a9c07b2449149181b387cb

* chore(build): drop vestigial -tags testing from Makefile and CI

The `testing` build tag was introduced in 461e3c6 to gate
extension/platform/register_testing.go (ResetForTesting); PR review
0efee93 then dropped the //go:build testing directive from that file
so downstream `go test ./...` would work without the tag, but never
cleaned the matching tag references out of Makefile and ci.yml.

The result: 8 places passing -tags testing for a tag that nothing in
the repo actually gates, plus a Makefile comment that confidently
claims a gate exists. Net behaviour is identical to omitting the flag;
the only effect is misleading developers into believing there is a
test-only surface separation.

Drop the flag from vet / unit-test / lint / coverage / deadcode (head
+ base worktree) and remove the misleading comment. ResetForTesting's
public-API exposure was the conscious trade-off taken in 0efee93 and
is left untouched.

Change-Id: If0cd78c87d4aec2a2533419fe75b01aae6b165fd

* feat(cmdpolicy): enrich denial Reason with attempted value + rule constraint

The envelope reason for command_denied previously told the caller WHAT
axis failed but not the concrete values on each side, so an AI agent
reading the envelope could not tell which command identity / risk /
path was attempted vs. which the rule permits. The natural temptation
was then to recommend modifying the rule -- exactly the wrong nudge,
since policy exists to prevent the agent from rewriting its own limits.

Each Reason now carries both the attempted value and the rule's
constraint:

  identity_mismatch:
    "command supports identities [user]; rule allows [bot]"
  domain_not_allowed:
    "command path \"drive/+upload\" not in allow list [docs/** contact/**]"
  command_denylisted:
    "command path \"docs/+delete-doc\" matched deny pattern \"docs/+delete-*\""
  risk_too_high / write_not_allowed:
    "command risk \"high-risk-write\" exceeds rule max_risk \"write\""
  risk_not_annotated:
    "command has no risk_level annotation; rule denies unannotated commands"
    (drops the prescriptive "set allow_unannotated=true" hint -- that
     belongs in docs, not in the engine's denial path)

Adds firstMatch() helper so command_denylisted can name the specific
glob that fired; matchesAny() now wraps firstMatch.

Regression test pins the substring contract per reason_code so future
"comment cleanup" cannot silently strip the values out again.

Change-Id: I17c7cc9411f58e3e43ade5e1ce875f3b7fe3e5ea

* fix(cmdpolicy): gofmt engine_test.go

CI fast-gate flagged the test added in 2eb0c2b as unformatted. Local
make unit-test had it cached; should have run `make vet` (which runs
gofmt-equivalent check via fmt-check) before pushing. Trivial 3-line
indent fix.

Change-Id: I42297ae59f607b97b32e976c9ec1c9ec4ab7de21

* feat(cmd): annotate risk_level on all hand-written cobra commands

Without this, any non-empty user-layer policy.yml (default
allow_unannotated=false) denies these commands with reason_code
risk_not_annotated -- bricking auth login, config init, profile use
etc. on first contact with a policy.

cmdpolicy/engine evaluation now resolves to the intended axis (deny
list / allow list / max_risk / identities) instead of failing closed
on the unannotated gate. Policy authors can write `max_risk: write`
or `allow: [auth/** config/** ...]` to express real intent.

Classification:
  read              auth status/check/list/scopes, config show /
                    policy show / plugins show, doctor, completion,
                    schema, profile list, event list/status/schema/
                    consume
  write             auth login/logout, config init/bind/remove/
                    default-as/strict-mode, profile add/remove/
                    rename/use, event stop/_bus, api (raw transit)
  high-risk-write   update (replaces the CLI binary; failure can
                    leave the install broken)

Notes:
- api standalone is conservatively `write`; per-call risk is unknown
  at parse time (raw transit), so static gating only enforces the
  write-class minimum.
- event _bus is the hidden IPC daemon forked by consume; standalone
  invocation by users is not expected, but the annotation keeps
  policy evaluation consistent with the other event subcommands.
- The two diagnostic-allowlisted commands (config policy show /
  plugins show) still bypass the engine via diagnosticPaths; the
  read annotation is for consistency with surrounding leaves.

---------

Co-authored-by: liangshuo-1 <266696938+liangshuo-1@users.noreply.github.com>
2026-05-18 15:25:02 +08:00
MaxHuang22 593025d298 feat: add SHA-256 checksum verification to install.js (#592)
* refactor: make install.js side-effect-free on require

Change-Id: I5444e3f34642d7c0740b6422a70ca6921a85e363

* feat: add getExpectedChecksum with unit tests

Change-Id: I87548be25d30c384e743da17b1d161b9d9f0ea87

* feat: add verifyChecksum with unit tests

Change-Id: Ifc2067bf1b824b02257dba7b53716fbe18d0f6b6

* feat: harden download with host allowlist and checksum verification

Change-Id: I2580782866049f1f62a2597e86b7bf59d0e50925

* ci: bundle checksums.txt in npm package for install verification

Change-Id: I2d7c44d9d5b9075158f63c0f8cf66c1e0abe3d8d

* ci: use triggering tag and verify checksums.txt presence in release workflow

Address CodeRabbit review: use GITHUB_REF_NAME instead of parsing
package.json to avoid version drift, and add explicit file check to
fail loudly if checksums.txt is missing or empty.

Change-Id: I8a5658412b6afc338ad2a642baba146cceafd0fc

* feat: streaming hash, allowlist tests, and malformed-line coverage

- verifyChecksum: switch from readFileSync to streaming 64KB chunks
  to avoid loading entire archive (10-100MB) into memory
- Export and test assertAllowedHost: 7 cases covering allowed hosts,
  rejection, case normalization, port handling, invalid URL
- Add ALLOWED_HOSTS comment clarifying it only gates initial URL
- Add getExpectedChecksum tests for malformed/tab-separated lines

Change-Id: Ida639def89c242b3b261a76effae08fd414a10dc
2026-04-23 19:40:27 +08:00
Yuxuan Zhao 1ad7cfab5b test: inject user env only for cli e2e user commands (#541) 2026-04-17 17:33:37 +08:00
Yuxuan Zhao 5280517d4b Feat/cli e2e tests with UAT (#528)
* test: expand and stabilize cli e2e workflows

* ci: run deadcode with test entrypoints
2026-04-17 16:57:17 +08:00
liangshuo-1 d0ab8ee7dc ci: consolidate workflows into layered CI pyramid with results gate (#510)
* ci: consolidate 6 workflows into layered CI pyramid with results gate

Merge tests.yml, lint.yml, coverage.yml, cli-e2e.yml, gitleaks.yml,
and license-header.yml into a single ci.yml with fail-fast layering:

- L1 fast-gate: build, vet, gofmt, go mod tidy
- L2 quality: unit-test, lint, coverage (40% threshold + Codecov), deadcode (incremental)
- L3 e2e: dry-run (no secrets) + live (with secrets, fork-skip)
- L4 security: gitleaks, govulncheck, go-licenses, license-header

Results gate aggregates all jobs as the single required check for
branch protection.

Also adds:
- arch-audit.yml: weekly cron for dead code, complexity, deps, E2E gaps
- .golangci.yml: depguard shortcuts-no-raw-http, forbidigo fmt.Print/log.Fatal
- AGENTS.md: E2E testing conventions, updated pre-PR checks

Change-Id: I2e21067a9e9e12d366d1b1a092227e9f7d60fe41
2026-04-16 18:16:31 +08:00
yaozhen00 c2b132945e feat(test): optimize cli-e2e-testcase-writer skill (#447)
* feat(test): optimize cli-e2e-testcase-writer skill add coverage.md

* feat(test): test report show
2026-04-13 21:10:11 +08:00
yaozhen00 67cb0a961e ci: add license-header check (#250)
* ci: add license-header check
2026-04-08 21:57:59 +08:00
williamfzc 2efadece34 feat: add scheduled issue labeler for type/domain triage (#251)
* ci: add issue labeler workflow

Add a manual GitHub Actions workflow and script to poll issues and apply type/domain labels.

* feat(issue-labels): refine heuristics and add docs

Improve domain detection and add safeguards to avoid overriding manual type triage by default. Refresh regression samples from real issues and document usage.

* ci(issue-labels): enable hourly scheduled labeling

Run hourly on schedule with write mode by default while keeping manual dispatch dry-run by default.

* ci(issue-labels): shorten lookback window to 6h

Reduce scheduled scan window while keeping overlap for missed runs.

* ci(issue-labels): opt into Node 24 actions runtime

Set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 and use Node 24 for the script runtime to avoid upcoming Node 20 deprecation warnings.

* ci(issue-labels): restore lookback input for manual runs

Allow workflow_dispatch to override lookback_hours while keeping hourly schedule fixed.

* ci(issue-labels): upgrade checkout/setup-node to v6

Use actions/checkout@v6 and actions/setup-node@v6 to align with Node 24 runtime and avoid Node 20 deprecation warnings.

* fix(ci): label only unlabeled issues via search api

* fix(ci): refine issue labeling heuristics from live issues

* fix(ci): address remaining issue label review comments

* fix(ci): fix issue label arg parsing regression

* docs(issue-labels): clarify one-shot unlabeled triage scope
2026-04-07 10:35:40 +08:00
yaozhen00 5cf866739d feat(test): Add a CLI E2E testing framework for lark-cli, task domain testcase and ci action (#236)
* feat: cli e2e test framework and demo

* feat: add cli-e2e-testcase-writer skill and task case

* feat: add cli e2e config and fix test resource prefix
2026-04-03 17:26:06 +08:00
iyaozhen 112dd5f6b2 ci: add gitleaks scanning workflow and custom rules (#142) 2026-04-02 16:51:20 +08:00
williamfzc 5621d2e555 feat(ci): refine PR business area labels and introduce skill format check (#148)
* feat(ci): add PR size label pipeline

* chore(ci): make PR label sync non-blocking

* feat(ci): add dry-run mode for PR label sync

* feat(ci): add PR label dry-run samples

* test(ci): update PR label samples with real historical merged PRs

Replaced synthetic or open PR samples with actual merged/closed PRs from the
repository to provide a more accurate reflection of the size label categorization.
Added 4 samples each for sizes S, M, and L covering docs, fixes, ci, and features.

* feat(ci): add high-level area tags for PRs

Based on user feedback, fine-grained domain labels (like `domain/base`) are too detailed for the early stages.
This change adds support for applying `area/*` tags to indicate which important top-level modules a PR touches.

Currently tracked areas:
- `area/shortcuts`
- `area/skills`
- `area/cmd`

Minor modules like docs, ci, and tests are intentionally excluded to keep tags focused on critical architectural components.

* refactor(ci): extract pr-label-sync logic to a dedicated directory

To avoid polluting the root `scripts/` directory, moved `sync_pr_labels.js` and
`sync_pr_labels.samples.json` into a new `scripts/sync-pr-labels/` folder.
Added a dedicated README to document its usage and behavior.
Updated `.github/workflows/pr-labels.yml` to reflect the new path.

* refactor(ci): rename pr label script directory for simplicity

Renamed `scripts/sync-pr-labels/` to `scripts/pr-labels/` to keep directory
names concise. Updated internal references and GitHub workflow files to point
to the new path.

* ci: add GitHub Actions workflow to check skill format

* test(ci): update sample json to include expected_areas

Added `expected_areas` lists to each sample in `samples.json` to reflect
the newly added `area/*` high-level module tagging logic. Allows testing
to accurately check both `size/*` and `area/*` outputs.

* refactor(scripts): move skill format check to isolated directory and add README

* test(scripts): add positive and negative tests for skill format check

* fix(scripts): revert skill changes and downgrade version/metadata checks to warnings

* fix(scripts): completely remove version check and skip lark-shared

* refactor(ci): improve pr-labels script readability and maintainability

- Reorganized code into logical sections with clear comments
- Encapsulated GitHub API interactions into a reusable `GitHubClient` class
- Extracted and centralized classification logic into a pure `evaluateRules` function
- Replaced magic numbers with named constants (`THRESHOLD_L`, `THRESHOLD_XL`)
- Fixed `ROOT` path resolution logic
- Simplified conditional statements and control flow

* ci: fix setup-node version in pr-labels workflow

* tmp

* refactor(ci): replace generic area labels with business-specific ones

- Add PATH_TO_AREA_MAP to map shortcuts/skills paths to business areas (im, vc, ccm, base, mail, calendar, task, contact)

- Replace importantAreas with businessAreas throughout the codebase

- Remove area/shortcuts, area/skills, area/cmd generic labels

- Now generates specific labels like area/im, area/vc, area/ccm, etc.

- Update samples.json expected_areas to match new behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): address PR review feedback for label scripts and workflows

- Add `edited` event to PR labels workflow to trigger on title changes
- Add security warning comment in pr-labels.yml workflow
- Update pr-labels README with latest business area labels
- Exclude `skills/lark-*` paths from low risk doc classification
- Handle renamed files properly in PR path classification
- Fix YAML frontmatter extraction to handle CRLF line endings
- Use precise regex for YAML key validation instead of substring match
- Fix exit code checking logic in skill-format-check test script
- Translate Chinese comments in skill-format-check to English

* fix(skill-format-check): address CodeRabbit review feedback

- Fix frontmatter closing delimiter detection to strictly match '---' using regex, preventing invalid closing tags like '----' from passing.
- Improve test fixture reliability by failing tests immediately if fixture preparation fails, avoiding false positives.

* fix: address review comments from PR 148

- ci: warn when PR label sync fails in job summary
- test(skill-format-check): capture validator output for negative tests
- fix(skill-format-check): catch errors when reading SKILL.md to avoid hard crashes

* fix: add error handling for directory enumeration in skill-format-check

- refactor: use `fs.readdirSync` with `{ withFileTypes: true }` to avoid extra stat calls
- fix: catch and report errors gracefully during skills directory enumeration instead of crashing

* docs(skill-format-check): clarify `metadata` requirement in README

test(pr-labels): add edge case samples for skills paths, CCM multi-paths, and renames

* test(pr-labels): add real PR edge case samples

- use PR #134 to test skill path behaviors
- use PR #57 to test multi-path CCM resolution
- use PR #11 to test track renames cross domains

* refactor(ci): migrate pr labels from area to domain prefix

- Replaced `area/` prefix with `domain/` for PR labeling to align with existing GitHub labels
- Renamed internal constants and variables from `area` to `domain` (e.g. `PATH_TO_AREA_MAP` to `PATH_TO_DOMAIN_MAP`)
- Updated `samples.json` test data to use new `domain/` format and `expected_domains` key
- Added `scripts/pr-labels/test.js` runner script for continuous validation of labeling logic against PR samples
- Corrected expected size label for PR #134 test sample

* test: use execFileSync instead of execSync in pr-labels test script

* fix: resolve target path against process.cwd() instead of __dirname in skill-format-check

* docs: correct label prefix in PR label workflow README

- Updated README.md to reflect the new `domain/` label prefix instead of `area/`

* fix(ci): fix dry-run console output formatting and enforce auth in tests

- Removed duplicate domain array interpolation in printDryRunResult
- Added process.env.GITHUB_TOKEN guard in test.js to prevent ambiguous failures from API rate limits

* fix(ci): ensure PR labels can be applied reliably

- Added `issues: write` permission to pr-labels workflow, which is strictly required by the GitHub REST API to modify labels on pull requests
- Reordered script execution in `index.js` to apply/remove labels on the PR *before* attempting to sync repository-level label definitions (colors/descriptions). The definition sync is now a trailing best-effort step with error catching so transient repo-level API failures don't abort the critical path.

* fix(ci): fix edge cases in pr-label index script

- Added missing `skills/lark-task/` to `PATH_TO_DOMAIN_MAP` to properly detect task domain modifications
- Updated GitHub REST API error checking in `syncLabelDefinition` to reliably match `error.status === 422` rather than loosely checking substring
- Moved token presence check in `main()` to happen before `resolveContext` to avoid triggering unauthenticated 401 API limits when GITHUB_TOKEN is omitted locally

* test(ci): clean up PR label test samples

- Removed duplicate PR entries (#11 and #57) to reduce redundant API calls during testing
- Renamed sample test cases to correctly reflect their expected labels (e.g. `size-l-skill-format-check` -> `size-m-skill-format-check`)

* fix(ci): bootstrap new labels before applying to PRs

- Prior changes correctly made full label sync best-effort, but broke the flow for brand new domains
- GitHub API returns a 422 error if you attempt to attach a label to an Issue/PR that does not exist in the repository
- Added a targeted bootstrap loop to create/sync specifically the labels in `toAdd` before attempting `client.addLabels()`
- Left the remaining global label synchronization as a best-effort trailing action

* test(ci): automate PR label regression testing

- Added a dedicated GitHub Actions workflow (`pr-labels-test.yml`) to automatically run `test.js` against `samples.json` whenever the labeling logic is updated
- Documented local testing instructions in `scripts/pr-labels/README.md`

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 17:45:39 +08:00
kongenpei 6463ab13c9 ci: make pkg.pr.new comment flow fork-safe (#170)
* ci: make pkg.pr.new comment flow fork-safe

* ci: harden trusted comment workflow inputs

* ci: skip comment steps when payload artifact is missing

* ci: use artifact PR number when workflow_run pull_requests is empty

* ci: allow PR comment workflow to write pull requests

---------

Co-authored-by: kongenpei <kongenpei@users.noreply.github.com>
2026-04-01 13:57:28 +08:00
kongenpei c4851a5c45 ci: improve pkg.pr.new install comment clarity (#168)
* ci: improve pkg.pr.new install comment clarity

* ci: add emojis to pkg.pr.new install comment headings

* ci: avoid hard fail when PR head metadata is missing

---------

Co-authored-by: kongenpei <kongenpei@users.noreply.github.com>
2026-04-01 11:15:54 +08:00
kongenpei bdd39b0196 ci: add pkg.pr.new PR preview workflow (#152)
* ci: publish PR preview builds to pkg.pr.new

* chore: limit pkg-pr-new build targets to 3 platforms

* ci: use node lts in pkg-pr-new workflow

* chore: trim pkg-pr-new to single target to fit size limit

* ci: publish pkg-pr-new package path without --bin

* ci: disable compact pkg-pr-new urls for fork previews

* ci: post minimal npm -g pkg-pr-new install comment

* chore: enable windows amd64 build for pkg-pr-new

* ci: format pkg-pr-new install comment as markdown code block

* ci: tweak pkg-pr-new comment wording

* ci: pin github-script and paginate PR comments

* chore: enable pkg PR build targets

---------

Co-authored-by: kongenpei <kongenpei@users.noreply.github.com>
2026-03-31 21:26:38 +08:00
qianzhicheng95 c35b1ae2c5 feat: add npm publish job to release workflow (#145)
* feat: add npm publish job to release workflow

Change-Id: Ibfae2af6bd2aabf09936c96d21964af98b77c127
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: bump package version to 1.0.1

Change-Id: Ifb58789be5621ab4979b5fe60e0e30042e07fea8
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:55:52 +08:00
liangshuo-1 e5a83f5eaa ci: improve CI workflows and add golangci-lint config (#71)
* ci: improve CI workflows and add golangci-lint config

- Add path filters to avoid unnecessary CI runs on non-Go changes
- Use go-version-file instead of hardcoded Go version
- Unify runners to ubuntu-latest
- Consolidate staticcheck/vet into golangci-lint with curated linter set
- Add go mod tidy check, govulncheck, and dependency license check
- Enable race detector in coverage, increase test timeout to 5m
- Add build verification step to tests workflow
- Add .codecov.yml with patch coverage target (60%)
- Add .golangci.yml (v2) with security and correctness linters

Change-Id: I409beb21cc1f1568ff47739c0a00f6214c10a0dd

* ci: replace Codecov upload with GitHub Job Summary coverage report

- Remove Codecov action dependency and CODECOV_TOKEN usage
- Generate coverage report using go tool cover and display in Job Summary
- Rename job from 'codecov' to 'coverage'
- Remove .codecov.yml from paths filter

Change-Id: Ib65dab6c4d7117c3300a9ea31eb1550537c72f88

* ci: trigger lint workflow

Change-Id: Ic1c492dd339f5460d2be2971ac65ea8f99e524eb

* ci: replace golangci-lint action with go run to avoid action whitelist restriction

Change-Id: I87274abf9780eb8b6350e98a27302ec5acc2a2e5

* ci: replace golangci-lint action with go run, keep incremental lint via --new-from-rev

Change-Id: I3d4a13cfd7b6c02e4098b04b8533a7248185c077

* ci: add fetch-depth 0 to lint checkout for incremental lint to work

Change-Id: I112279c5ec06dc0aa3aa7e01d564ea27fbd20533

* ci: disable errcheck linter due to high volume of existing violations

Change-Id: Iec57e8fbe42699f687d931d9dde2f879f2ae5b02

* ci: align golangci-lint config with GitHub CLI, make govulncheck non-blocking

- Add exptostd, gocheckcompilerdirectives, gochecksumtype, gomoddirectives linters
- Move gosec, staticcheck, errname, errorlint, misspell to TODO for later enablement
- Remove G104 exclusion (errcheck is disabled)
- Make govulncheck continue-on-error until Go version is upgraded

Change-Id: I330ece4f202229aee1e2f50790f6b22738704c05

* ci: fix go-licenses module path for v2

Change-Id: Ifd018ebe79cd18402171417b1b73313af2d23c6d
2026-03-30 11:09:31 +08:00
梁硕 83dfb068ad feat: open-source lark-cli — the official CLI for Lark/Feishu
Change-Id: I113d9cdb5403cec347efe4595415e34a18b7decf
2026-03-28 10:36:25 +08:00