Files
dingtalk-workspace-cli/AGENTS.md

20 KiB

Repository Agent Guide

This file applies to the entire repository. Keep changes scoped, preserve unrelated work, and use gofmt for every modified Go file.

Build and test

  • Build: go build ./cmd
  • Full test suite: DWS_PACKAGE_VERSION=0.0.0-test go test ./...
  • Generate Schema assets: go generate ./internal/cli (entry point: internal/cli/gen.go)
  • Refresh pinned MCP metadata: make fetch-mcp-metadata (requires dws auth login)
  • Check generated drift: ./scripts/policy/check-generated-drift.sh
  • Check the Schema contract: ./scripts/policy/check-schema-catalog.sh

Generated Schema JSON is committed. Change its source inputs and generators, then regenerate; do not hand-edit generated Catalog or Agent metadata files. internal/cli/schema_command_registry.json is different: it is a reviewed CommandRegistry source, not a generated snapshot. It is the single reviewed source of stable canonical identity, primary paths, aliases, and navigation. Edit it only when reviewed exposure, identity, primary path, or aliases change; parameter, Skill, and metadata-only changes must not rewrite it mechanically.

Agent Schema contract

The Schema data flow is one way:

1. app.NewRootCommand()
   └─ builds the real Cobra command tree and flags

2. schema_command_registry.json
   + schema_hints/metadata/<product>.json tool parameters (+ cli_path)
   └─ forms EffectiveCommandRegistry
      └─ binds exactly to real Cobra leaves and aliases

3. Parameter resolution
   Cobra flags
   + schema_parameter_bindings.json
   + metadata tool parameters
   └─ produces ParameterSpec and constraints

4. Agent and interface semantics
   schema_hints/selection/<product>.json   (selection prose)
   + schema_hints/metadata/<product>.json  (safety/interface/runtime_gate)
   + pinned MCP metadata
   └─ resolves Agent metadata by source precedence
      Markdown is evidence only; it is not concatenated into final prose

5. One typed hub
   BoundCommandRegistry
   + ParameterSpec
   + Agent metadata
   + Interface metadata
   └─ resolves every command exactly once into ToolSpec
      └─ aggregates SchemaRegistry + SchemaIndex

6. One-way publication
   SchemaRegistry
   └─ internal/cli/schema_catalog/
      (catalog.json + tools/<product>.json; split per product so
       concurrent feature PRs only rewrite their own shard)
      └─ dws schema list/product/group/leaf/--all

7. Runtime consumption (unified API)
   ResolveMeta(cliPath) → CommandMeta{Identity, Safety, Selection}
   └─ internal/cli/command_meta.go
   └─ all consumers (help, schema, agent, skill-gen) call this one function
   └─ backed by embedded catalog (sync.Once lazy map, O(1) lookup)

Parameter overlays from metadata are merged into EffectiveCommandRegistry before Cobra binding; after that point there is no second identity source and no identity precedence winner. The binder must reject a missing/non-runnable Cobra path, an alias collision, and any native identity annotation that disagrees with the effective registry. A missing native identity annotation is allowed because annotations are implementation-side assertions, not identity fallbacks.

The assembler resolves every bound command exactly once into one ToolSpec. Build-time gates and the snapshot serializer consume that source-resolved typed registry/index. Runtime projections and delivery gates consume the typed registry/index returned by the production snapshot loader. Neither path may reopen annotations, merge source records, or use a previous Catalog or other generated JSON as a source. schema_catalog/ (catalog.json + per-product tools/.json shards) is output-only in the generation graph. The production loader decoding the embedded published snapshot is a delivery boundary, not source resolution; it must never create or repair a Cobra command, flag, registry entry, or later Catalog generation.

Generation vs consumption separation

The Schema system has two physically separated processes:

Generation (build-time, slow, reviewed, one-way):

  • Entry point: internal/cli/gen.go (all //go:generate pragmas isolated here, not in business code).
  • Tools: internal/generator/cmd_schema_agent_metadata + cmd_schema_catalog + cmd_param_aliases (standalone Go mains).
  • Inputs: 7 authored source groups (registry + hints metadata + hints selection + MCP metadata + parameter bindings + reviewed parameter concepts + cobra tree).
  • Output: schema_catalog/ (per-product shards) + schema_agent_metadata/ + param_aliases_generated.go.
  • Refresh MCP metadata: make fetch-mcp-metadata (iterates 26 MCP server endpoints, merges with previous data for cross-server interface_ref).
  • Gates: make generate-schema (byte guards on inputs), check-generated-drift.sh, check-command-surface.sh (catalog structure).

Consumption (runtime, fast, read-only, unified API):

  • Entry point: ResolveMeta(cliPath) → CommandMeta{Identity, Safety, Selection} in internal/cli/command_meta.go.
  • Backed by embedded catalog (embeddedSchemaCatalog(), sync.Once, O(1) map).
  • Consumers: --help (Safety annotation via RenderSafetyAnnotation), dws schema, agent selection, future skill generation.
  • SafetyForCLIPath delegates to ResolveMeta (backward compatible).

This split is architecturally isomorphic to Lark's typed metadata registry, navigation catalog, and schema renderer. DWS intentionally preserves its existing flat JSON wire contract for compatibility; do not treat architectural alignment as permission to make an unversioned wire-format change.

The reviewed CommandRegistry is the sole source of stable command identity and navigation. The executable Cobra tree remains the source of truth for whether a CLI path exists, is runnable, and which flags it accepts. Schema coverage is bidirectional:

  1. Every final SchemaRegistry tool, including its serialized Catalog projection, must resolve to an executable Cobra command.
  2. Every public runnable Cobra leaf must either resolve to Schema or appear as an exact, reviewed exclusion with a non-empty reason in internal/cli/schema_command_exclusions.json.

Do not use prefix or wildcard exclusions: they can silently hide future commands. Remove an exclusion when its command enters Schema; stale, invalid, or duplicate exclusions must fail generation and CI.

When adding or changing an Agent-visible command, review all relevant inputs:

  • internal/cli/schema_command_registry.json for the reviewed CommandRegistry: canonical identity, primary CLI path, aliases, and stable navigation. It is the identity source and is not a generated artifact.
  • internal/cli/schema_command_registry.schema.json is its closed, machine-readable editing contract. Preserve the local $schema reference; unknown fields, invalid visibility values, stale paths, and collisions fail Go validation and policy.
  • internal/cli/schema_hints/metadata/<product>.json for safety, interface, runtime_gate, and optional parameter overlays (parameters / cli_path).
  • internal/cli/schema_hints/selection/<product>.json for reviewed Agent selection prose (agent_summary, use_when, avoid_when, examples).
  • internal/cli/schema_hints/index.json only maps product IDs to those files.
  • Native Runtime Schema identity annotations, when present, as consistency assertions against EffectiveCommandRegistry. They must agree exactly and must never materialize, infer, or override registry identity.
  • Flag-to-interface property mappings and required/default semantics.
  • Generated files under internal/cli/schema_agent_metadata/ and internal/cli/schema_catalog/ (catalog.json + tools/.json) after running generation.

Run the reverse-completeness tests whenever the Cobra tree changes. A command that works through dws <path> but cannot be found through the matching dws schema lookup is a contract failure unless it has a reviewed exact exclusion.

Metadata parameter overlays must reference an exact public runnable Cobra leaf and real flags. They may override Schema description, interface-property/type mapping, required, and required_when; they must not create commands or flags, define an interface, or advertise an unknown RPC. Every authored entry requires reviewed: true and a non-empty review reason.

For Agent-authored metadata or selection edits:

  1. Confirm the exact command and flag names in the current Cobra tree.
  2. Edit only the owning block (metadata/ or selection/); do not mix fields.
  3. Add the smallest possible entry; do not copy generated Catalog fields into the input.
  4. Describe user-visible semantics in review_reason and parameter descriptions.
  5. Run generation, drift, Schema policy, and the focused CLI tests before proposing the change.

Agent curation workflow (Schema hints)

Use this workflow when refreshing Agent selection prose and confirmation alignment. Prefer agent-authored review over bulk merge scripts that dump selection-review.json or Skill Markdown into Catalog fields.

Human-authored inputs are split into two blocks:

Block Path Owns
metadata internal/cli/schema_hints/metadata/<product>.json effect / risk / confirmation / idempotency / interface_* / runtime_gate / optional parameters
selection internal/cli/schema_hints/selection/<product>.json agent_summary / use_when / avoid_when / examples (+ product routing)

index.json only maps product IDs to those files. Do not mix selection fields into metadata files or metadata fields into selection files.

Goals

  1. Selection prose is decision-oriented (Feishu/Lark style): trigger intent, sibling-command routing, and outcome shape — not a restatement of the summary. Delivered Catalog provenance is reviewed_explicit from selection/.
  2. Safety follows Runtime: confirmation=user_required iff the tool's metadata runtime_gate != none (for example confirm_delete, typed_yes, confirm_dangerous).
  3. Parameter overrides (former Manual commands) live on metadata tools as parameters (+ cli_path) and are applied into EffectiveCommandRegistry.

Authoring

For every curated tool:

  1. Edit metadata/<product>.json for safety/interface/gates/parameters.
  2. Edit selection/<product>.json for selection prose (reviewed: true, review_reason, source_refs).
  3. Run make generate-schema. Do not hand-edit generated schema_agent_metadata/ or schema_catalog/.

Pull live MCP descriptions (personal token)

Pinned internal/cli/schema_mcp_metadata.json is a sanitized baseline. Prefer live Schema from a logged-in personal session:

dws auth status                 # token_valid should be true
dws cache refresh               # refresh discovery / tools cache
dws schema <mcp-canonical> -f json
# or CLI path: dws schema --cli-path "drive copy" -f json

Resolve MCP identity via interface_ref when CLI canonical ≠ MCP path (example: CLI drive.copy_document → live doc.copy_document). On pull failure, fall back to Skill + Cobra Help + pinned MCP, and record evidence (for example live-dws-schema:<path>#FAILED). Never print or commit tokens.

Precedence when sources disagree: Runtime/Cobra > live MCP > pinned MCP > Skill (evidence only).

Parallel product agents

Split work by product groups. Each agent must:

  • Read Skill, Cobra/--help, Runtime confirmation sites, and live dws schema for its tools.
  • Hand-write selection + metadata; forbid wholesale JSON merges from review dumps.
  • Edit only its metadata/<product>.json and selection/<product>.json.
  • Never git checkout unrelated product files to “clean scope”.

Regenerate and gates

make generate-schema
./scripts/policy/check-runtime-confirmation-truth.sh
go test ./internal/app -run '^TestSheetFinalSchemaConfirmationMatchesRuntimeGuards$' -count=1

Example rules (fail generation otherwise):

  • At most two examples per tool; no --yes in stored examples.
  • Examples must match live Cobra argv (path, flags, required groups).
  • No shell comments in examples.

After generation, spot-check Catalog: selection provenance is reviewed_explicit from selection/, and user_required count equals metadata runtime_gate != none.

make generate-schema is a full deterministic snapshot rebuild, not an incremental patch over the previous Catalog. It rereads every reviewed input, removes stale generated product metadata, and rewrites the exact metadata and Catalog projections. Incremental work happens only when an Agent or human edits selected metadata/ or selection/ entries; the next publication still recomputes all outputs. Generated files must never be read back as merge input, and byte guards fail generation if it changes the hint inputs or CommandRegistry.

Selection prose may choose a more or less restrictive recommendation. It cannot create a Cobra command or flag, change parameter facts, invent an RPC/interface, alter safety metadata, or bypass command completeness. Examples must use an executable primary/alias path and flags accepted by the live Cobra command; never add --yes to stored examples.

Every example is always checked against its real BoundCommand: exact path, accepted flags, Cobra required flags/positionals, and the effective require_one_of, require_together, and mutually_exclusive constraints must all pass before execution eligibility is considered. A missing required value, constraint failure, runtime error, or MCP resolution error is a contract bug; none is a valid reason to skip an example.

Example execution defaults to contract validation only. Runtime execution is opt-in: an example enters dry_run only when its final ToolSpec publishes an explicit reviewed dry-run capability. The test never injects --yes, and risk/confirmation values do not manufacture preview support. A narrow runtime precondition that cannot be derived from the typed contract may use an exact zero-based example_dispositions entry with mode=contract_only, reviewed=true, one of the schema-enumerated reason codes, and a concrete non-empty reason. Such a disposition may only narrow an explicit dry-run capability; it cannot turn an ordinary contract-only example into a skip. Duplicate, missing, and out-of-range indexes fail validation. Never catch a dry-run failure and dynamically downgrade it to contract_only.

Normal Go tests run the exhaustive contract gate. Run make test-schema-agent-examples to additionally execute the eligible subset through the real Cobra --dry-run path with isolated HOME and blocked proxies. The test reports stable total, contract, dry_run, contract_only, reviewed_manual, and per-reason counts; changing those counts requires a review of the corresponding typed dry-run capability or manual disposition. This target is also part of make policy.

Treat every tool use_when entry as a reviewed positive selection scenario whose expected result is that tool's canonical path, and every avoid_when entry as a reviewed negative scenario that must not choose that tool. The deterministic gate derives a typed evaluation fixture from these same fields; it requires exact tool coverage, a real runnable BoundCommandRegistry primary command, at least one positive and negative assertion per tool, and no literal contradictory expectations. It does not claim that string matching proves natural-language understanding.

Semantic selection is an explicit opt-in live-model check. Run the smoke set (one positive and one negative scenario per product) with DWS_AGENT_SELECTION_LIVE=1 ARK_API_KEY=... ARK_BASE_URL=... ARK_MODEL=... go test ./internal/app -run TestManualAgentSelectionArkLive -count=1. Add DWS_AGENT_SELECTION_FULL=1 to evaluate every committed tool scenario, or set DWS_AGENT_SELECTION_CASES to comma-separated fixture case IDs. Normal CI never calls a model; its blockers remain the reproducible fixture, binding, example, provenance, and final-delivery facts.

The live evaluator sends only case IDs/scenarios plus one same-product candidate table; expected/forbidden assertions stay local and must never be included in the model prompt. Built-in Ark HTTPS bases are allowlisted. A different HTTPS provider requires its exact base in DWS_AGENT_SELECTION_ALLOWED_BASE_URLS; plaintext HTTP is accepted only for a loopback test server so API credentials are never sent to an arbitrary clear text endpoint.

Safety metadata

Parameter and safety resolution is mostly source-precedence based and value-neutral: do not choose a winner because one value looks stricter. A higher-priority reviewed metadata/explicit source may intentionally raise or lower description, mapping, effect, risk, confirmation, or idempotency. Preserve all candidates and the selected source in provenance, and fail same-precedence conflicts rather than silently merging them.

required is the exception. Cobra MarkFlagRequired is a hard floor: the final Agent projection must keep required=true and cannot be lowered by manual/hint overlays. Overlays may still raise an optional flag to required. cli_required continues to mirror the executable Cobra marker.

For command text, reviewed ToolSchemaHint wins first, then command-specific Cobra Help, then MCP metadata. Generic RPC prose may remain an unselected provenance candidate (and parameter-level interface_description); it must not overwrite a specialized leaf's title or description.

For every delivered ToolSpec and ParameterSpec field, the provenance winner value must exactly equal the delivered value. Checking only source, count, presence, or hash is not a sufficient final-delivery invariant.

The same resolved ToolSpec must drive every projection. The full leaf payload must equal the corresponding tool in schema --all and the full Catalog tool. Overview/product/group summaries and Catalog summaries must equal ToolSpec.ToSummaryPayload(). An alias lookup may change only the view fields cli_path and is_alias; it must not re-resolve or mutate the command contract.

This build-time rule is distinct from runtime drift handling. If shipped Help and leaf Schema disagree, pass only flags accepted by Cobra. For conflicting safety information, do not silently take the less restrictive behavior: use the safer interpretation or stop and report the contract drift.

Do not infer one safety field from another. In particular, effect=destructive or risk=high does not mechanically rewrite confirmation; the final precedence winner for each field is authoritative. When confirmation=user_required, obtain confirmation before adding --yes. Keep CLI confirmation behavior and Schema metadata consistent, and add a semantic regression test through the final embedded loader/query delivery path; a generator unit test or JSON count alone is insufficient.

Current Schema boundaries

  • schema list remains a progressive overview. schema --all is the stable full-export contract: every final SchemaIndex tool must contain its complete leaf parameters, constraints, and safety semantics, including an empty parameters object for commands without flags. Keep it suitable for the #602 compatibility baseline and fail rather than silently emitting a partial export.
  • schema --all is not normal command discovery. Use overview -> product/group -> leaf for routine Agent work. --compact is supported for context-saving projections, but a compact full export is not a complete compatibility baseline.
  • dws <path> --help defines whether Cobra exposes a path and which flags the executable accepts. A leaf Schema defines Agent selection, parameter mapping and constraints, and safety/confirmation semantics. A conflict is contract drift, not permission to guess.
  • Schema and Help describe commands; neither returns DingTalk business data. After discovery, execute the real read/search/list command to obtain data.