Files
dingtalk-workspace-cli/docs/helpers-structure-guide.md
玉澜andCursor ad0d2b5012 docs: add helpers package structure guide to coding agent harness
Route business teams and agents to a flat package helpers layout with
thin {product}.go wiring and {product}_{resource}.go files, and forbid
growing megafiles like chat.go. Keep the harness check local and opt-in.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:40:40 +08:00

8.8 KiB
Raw Permalink Blame History

Helpers Package Structure Guide

This is the coding-structure contract for product commands under internal/helpers/. Business teams and coding agents must follow it when adding or moving CLI leaves. Behavioral contracts (stdout, errors, confirmation, Schema) stay in internal/helpers/AGENTS.md; this document only owns file layout and split rules.

Reference implementations already in-tree:

Pattern Use as
sheet.go + sheet_*.go Preferred product layout: thin root wiring, resource files
chat_media_upload.go Incremental extract from a megafile without behavior change
connect_*.go Concern-based split inside the same helpers package

Anti-pattern to stop growing: single megafiles such as chat.go / aitable.go (thousands of lines). New work must not enlarge them.

1. Package boundary

Keep product handlers in the flat package helpers:

internal/helpers/                 # package helpers (default)
  register_products.go            # public product registration table
  {product}.go                    # product root: new{Product}Command()
  {product}_{resource}.go         # one resource / cohesive concern
  {product}_{resource}_test.go    # focused tests beside the file
  helpers.go / interfaces.go …    # cross-product shared machinery

Do not create internal/helpers/{product}/ subpackages by default. The flat package exists so leaves can share unexported helpers (callMCPToolOnServer, flag validators, confirmation wrappers, transport adapters) without exporting a public API surface. Split into a subpackage only when the concern is a real library boundary with its own tests and almost no need for helpers-private symbols—and get an explicit review for that exception.

Repository layers outside helpers stay unchanged:

Layer Owns
internal/app Root/static wiring, plugin load
internal/helpers Product Cobra trees and handler behavior
internal/cobracmd Shared Cobra construction primitives
internal/executor, internal/transport Invocation and transport
internal/output, internal/errors, internal/safety Projection, failures, confirmation
internal/cli Schema identity / Agent metadata

Ordinary product leaves belong in helpers. Do not push product business mapping into app, transport, or Schema generators.

2. File roles inside a product

2.1 Product root — {product}.go

Owns exactly one entry constructor, registered from register_products.go:

func newChatCommand() *cobra.Command { /* wire subgroups only */ }

The root file should:

  1. Define the product cobra.Command (Use / Short / Long / aliases).
  2. Call resource factories and AddCommand them.
  3. Register product-level aliases or hint stubs when needed.
  4. Avoid large inline RunE bodies for leaves.

Target size: wiring-only, roughly under 400 lines (see sheet.go ≈ 270). If the root grows past that because leaves are inlined, extract a resource file.

2.2 Resource / concern file — {product}_{resource}.go

Split on the CLI path segment or cohesive concern, not on “one function per file”:

CLI path File
dws chat group … chat_group.go
dws chat message … chat_message.go
dws chat media … chat_media_upload.go (or chat_media.go)
dws sheet filter-view … sheet_filter_view.go
dws sheet dimension … sheet_dimension.go

Each file exposes one or more unexported factories, for example:

func newChatGroupCmd() *cobra.Command { … }
func newChatMessageCmd() *cobra.Command { … }
func newWorkbookCmds() []*cobra.Command { … }

The product root only wires those factories. Prefer keeping a leaf’s flags, RunE, and nearby request-mapping helpers in the same resource file until a helper is reused by multiple resources.

Soft size guide per resource file:

Lines Action
< 600 Normal
600–1000 Prefer splitting the next cohesive subgroup before adding more
> 1000 Required split before landing substantial new leaves

“One command per file” is not required. Tiny sibling leaves that share flags and mapping belong together. Split when the file holds multiple unrelated resources or becomes hard to review.

2.3 Shared product helpers — {product}_{concern}.go

Use a concern suffix when code is shared across resources and is not itself a command tree:

  • sheet_validate.go — shared validation
  • chat_args.go / similar — grant/arg builders used by several leaves
  • connect_command.go — slash-command parsing used by the daemon

Do not park unrelated products’ utilities in these files. Cross-product machinery belongs in non-prefixed shared files (helpers.go, interfaces.go, output/error helpers), never copied per product.

2.4 Tests

  • Name tests after the file or scenario: chat_message_search_test.go, sheet_filter_view_test.go.
  • Prefer exercising public product construction (newChatCommand().Find(…)) for command-surface regressions.
  • Pure helpers may be unit-tested directly in the same package.
  • A mechanical file split without behavior change should keep existing tests green with no assertion edits; if tests must change, the split leaked a behavior or visibility change and needs review.

3. Registration and naming

  1. Public products enter the CLI only through the table in register_products.go (new{Product}Command factories). Do not invent a second registration path for ordinary product leaves.
  2. Constructor names stay unexported (new…) unless a deliberate test helper requires otherwise.
  3. File names are lowercase snake_case. Match the CLI resource token when practical (filter-view → filter_view).
  4. Do not hand-edit generated sync markers in register_products.go outside the product-registration workflow that owns that file.

4. How to add a new command (business-team checklist)

  1. Confirm the owning product and CLI path with dws <product> --help.
  2. Open {product}.go only to wire AddCommand; put the leaf in {product}_{resource}.go.
  3. If the product is still a megafile (chat.go, aitable.go, …) and your resource file does not exist yet, create the resource file and move only the subgroup you need (or add the new leaf there and wire it from the root). Do not append another large leaf into the megafile.
  4. Keep stdout / stderr / error / confirmation contracts from internal/helpers/AGENTS.md.
  5. If path, flags, safety, or Agent selection change, follow schema-contributor-guide.md.
  6. Add or extend the closest test; run go test ./internal/helpers -count=1 (or a tighter -run) plus the checks selected by coding-agent-guide.md.

5. Migrating an existing megafile

Mechanical splits are welcome and preferred over “big-bang” rewrites.

Rules for a split PR:

  1. Behavior-neutral: same command paths, flags, help text, request mapping, confirmation, and output.
  2. Stable entrypoint: keep new{Product}Command() as the registration symbol; only its body becomes wiring.
  3. Move by resource: extract one CLI subgroup per commit/PR when possible (group, message, category, …).
  4. No drive-by cleanups in the same PR (renames, flag redesign, Schema edits) unless the task explicitly includes them.
  5. Stop growing the megafile: after the first extract, new leaves for that resource go to the new file only.

Suggested first-wave split for chat (illustrative, not mandatory order):

Extract to Contents
chat.go newChatCommand() wiring only
chat_permission.go chmod, data-auth
chat_group.go group tree
chat_message.go message tree
chat_category.go category tree
chat_bot.go bot tree
chat_conversation.go top-level conversation ops (set-top, mute, red-point, …)
existing chat_media_upload.go keep; optionally rename to chat_media.go only in a dedicated rename PR

Apply the same pattern to aitable, attendance, mail, and doc when those products take new work.

6. What this guide does not change

  • Wire format, MCP method names, or Schema identity rules.
  • The choice to keep package helpers flat.
  • Runtime confirmation / dry-run policy (still owned by safety + Schema metadata).
  • Permission to skip tests because a change was “only a move”—moves still need the product’s focused tests green.

7. Harness check

After editing this guide or its links from AGENTS.md / internal/helpers/AGENTS.md, run:

make coding-agent-harness

This check is a local, opt-in agent aid; it is not part of make policy or CI.