Compare commits

...
Author SHA1 Message Date
克谨 074106d35c Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-24 14:52:57 +08:00
克谨 1e9ad757d2 ci: split oversized app race shard 2026-08-24 14:50:56 +08:00
github-actions[bot] 19be571574 Merge pull request #1107 from DingTalk-Real-AI/codex/devdoc-hrbrain-pat-shortcuts
feat(shortcut): harden Devdoc HRbrain and PAT surfaces
2026-08-24 14:24:48 +08:00
克谨 bd74cadbf2 test(helpers): avoid Windows mousetrap coverage timeout 2026-08-24 14:13:08 +08:00
克谨 24bcd354cb feat(cli): complete parameter hallucination fallback coverage 2026-08-24 13:35:59 +08:00
Dennis b5a287ae71 feat(shortcut): harden devdoc hrbrain and pat surfaces 2026-08-24 13:10:38 +08:00
github-actions[bot] da6f867dfa Merge pull request #1085 from typefield/feat/drive-permission-pagination
feat(drive,doc,wiki): permission/member list pagination and multi-type members
2026-08-24 12:17:02 +08:00
zengyouling.zyl 82dc2b5e5e Merge remote-tracking branch 'upstream/main' into feat/drive-permission-pagination 2026-08-24 11:57:48 +08:00
zengyouling.zyl 9265fd4cb8 fix(skill): point wiki member pagination at native wiki member list 2026-08-24 11:57:36 +08:00
github-actions[bot] e324ef9d4a Merge pull request #1069 from WHUTzju/feat/add-aitable-datasource-tools
Feat/add aitable datasource tools
2026-08-24 11:33:54 +08:00
zengyouling.zyl fb1847d62e Merge remote-tracking branch 'upstream/main' into feat/drive-permission-pagination 2026-08-24 11:15:26 +08:00
zengyouling.zyl 331681e82b ci: retrigger auto-cr to pick up screenshot evidence 2026-08-24 11:05:14 +08:00
陌渊 1633888290 Merge upstream/main: resolve shortcut count conflict + fix field-ids doc
- schemaPublishedShortcutCount: 461→468 after merging aisearch/contact/live
  shortcuts from upstream/main
- Fix P2: datasource-update --field-ids doc says "不传时同步全部字段" but
  actual behavior keeps existing field config; fixed in usage guide and
  reference
2026-08-24 10:54:58 +08:00
github-actions[bot] 206f33ae1c Merge pull request #1083 from DingTalk-Real-AI/codex/shortcut-aisearch-contact-live
feat(shortcut): harden AiSearch Contact and Live task surfaces
2026-08-24 10:44:39 +08:00
陌渊 9259477372 [WP-46-001] fix: update shortcut count constants to match merged sheet/whiteboard shortcuts
publicShortcutCount 422→424, schemaPublishedShortcutCount 460→462,
publiclyDeliveredShortcutCount 422→424
2026-08-24 10:31:32 +08:00
Dennis 137151b38c fix(shortcut): align reviewed live response shapes 2026-08-24 09:57:54 +08:00
陌渊 081220f15e Merge branch 'main' into feat/add-aitable-datasource-tools 2026-08-24 09:50:50 +08:00
Dennis b8216380de fix(aisearch): declare stable result identity 2026-08-23 20:35:11 +08:00
Dennis 83bc213b7f fix(aisearch): reject non-person search sources 2026-08-23 19:40:27 +08:00
Dennis 804b9a3142 fix(contact): normalize exact mobile lookup input 2026-08-23 19:40:26 +08:00
Dennis cfdb0d0556 fix(contact): preserve legacy role placeholders 2026-08-23 19:40:24 +08:00
Dennis d8686122ab fix(schema): reconcile shortcut counts after rebase 2026-08-23 19:40:21 +08:00
Dennis 222a0230ae fix(contact): preserve strict list roles compatibility 2026-08-23 19:40:18 +08:00
Dennis f7befc7943 fix(contact): preserve roster CLI compatibility 2026-08-23 19:40:15 +08:00
Dennis 1dc924af1b fix(contact): align mobile catalog semantics 2026-08-23 19:40:12 +08:00
Dennis 0e8d6e00cf fix(contact): avoid unnecessary mobile detail lookup 2026-08-23 19:40:09 +08:00
Dennis 0028ed1570 fix(contact): restore exact mobile lookup 2026-08-23 19:40:06 +08:00
Dennis 8b5d9a59b0 fix(contact): preserve published schema interface 2026-08-23 19:40:03 +08:00
Dennis 01d663f597 fix(contact): verify exact mobile ownership 2026-08-23 19:40:01 +08:00
Dennis 2bf314c625 fix(contact): preserve list-roles CLI visibility 2026-08-23 19:39:59 +08:00
Dennis 8c98d1abe4 fix(shortcut): harden AiSearch Contact and Live delivery 2026-08-23 19:39:56 +08:00
Dennis 5e4a65513b fix(aisearch): fail closed on unprovable zero results 2026-08-23 19:39:54 +08:00
Dennis 4ae0e0ffc3 fix(contact): close exhaustive shortcut release gate 2026-08-23 19:39:52 +08:00
Dennis 1c9a977d08 fix(shortcut): close residual search and contact gaps 2026-08-23 19:39:50 +08:00
Dennis 78fabec4bb feat(shortcut): fail close Live list task 2026-08-23 19:39:47 +08:00
Dennis 6d6404993a feat(shortcut): harden Contact task surface 2026-08-23 19:39:45 +08:00
Dennis 008d50bb3d feat(shortcut): harden AiSearch task surface 2026-08-23 19:39:40 +08:00
zengyouling.zyl f871689960 ci: retrigger checks after flaky race shard and transient status upload 2026-08-23 19:34:04 +08:00
zengyouling.zyl b15a21de93 Merge remote-tracking branch 'upstream/main' into feat/drive-permission-pagination 2026-08-23 19:00:08 +08:00
github-actions[bot] 58e8e35948 Merge pull request #1098 from typefield/fix/schema-compat-confirmation-exceptions
fix(ci): review batch remove confirmation hardening
2026-08-23 18:52:41 +08:00
zengyouling.zyl a8b0d8895b fix(ci): review batch remove confirmation hardening
Rebuild the exact-entry reviewedCompatibilityExceptions carve-out in the
base-owned schema-compat checker for the three destructive batch-remove
tools whose confirmation PR #1085 tightens from not_required to
user_required (doc/doc.remove_permission, drive/drive.permission_remove,
wiki/wiki.remove_member). Because the compatibility gate builds its
checker from the PR merge-base, this carve-out has to land on main before
PR #1085 can pass; the entry set is exact (tool + field + old -> new), so
any other confirmation drift, including weakening a reviewed tool back to
not_required, still fails.
2026-08-23 18:33:32 +08:00
zengyouling.zyl 546c2d2eb2 fix(helpers): require user confirmation for batch permission/member remove
Address the P1 review finding on PR #1085: --members lets one call remove
up to 30 USER/DEPT/CONVERSATION/TAG members, where departments, chats,
and role groups can indirectly affect many more users, yet the remove
branches called the MCP tool right after argument parsing with Safety
confirmation=not_required.

- drive permission remove, doc permission remove, and wiki member remove
  now declare confirmation=user_required. DeclareLeafMetadata installs
  the ConfirmSafety gate automatically (deferred to the first
  deps.Caller.CallTool so flag validation still fails first), so an
  unconfirmed invocation exits with the typed confirmation_required
  error and performs zero MCP calls; --yes, an interactive yes, or
  --dry-run previews remain the supported paths.
- Pass framework confirmation errors through WrapErrorWithOperation
  verbatim (new apperrors.IsConfirmationRequired). Text classification
  misrouted them: command paths containing "permission" (drive/doc
  permission remove) were re-reported as AUTH_PERMISSION_DENIED while
  other paths (wiki member remove) lost their reason and degraded to
  UNCLASSIFIED.
- Tests: TestPermissionMemberRemoveRequiresConfirmationBeforeToolCall
  covers all three entry points for both --members and legacy --users —
  unconfirmed rejects with zero MCP calls, --yes dispatches exactly one
  call with the complete precise arguments, --dry-run previews without
  calls. Existing remove tests inject root --yes for the assembly
  assertions; blank --users still fails validation before confirmation.
2026-08-23 17:55:33 +08:00
zengyouling.zyl 5bd0ea7c53 fix(helpers): drop NO_PERMISSION from document permission codes
Address the P2 review finding on PR #1085: NO_PERMISSION is a generic
code name also returned by non-document tools — attendance
get-self-setting (bossAttendStatNotify) and event-subscription attempts
have both been observed returning it — so keying drive permission
apply-* guidance on it would mislead those products, defeating the goal
of the P1 scoping fix. Only the drive-specific forbidden.* domain codes
(forbidden.no.auth / forbidden.accessDenied) and the role-threshold
message wording remain document signals; a bare NO_PERMISSION still
classifies as AUTH_PERMISSION_DENIED but now keeps the product-neutral
suggestion, and NO_PERMISSION combined with document wording still gets
apply guidance.

Add regression tests for the non-document NO_PERMISSION case and update
the changelog fragment; changed-code coverage stays at 100%.
2026-08-23 17:01:49 +08:00
zengyouling.zyl 4833b39071 fix(helpers): scope permission-apply guidance and null->{} rendering to confirmed tools
Address the two P1 review findings on PR #1085:

- Permission suggestions: the drive permission apply-* guidance is now
  limited to document/wiki-specific errors (node access codes
  NO_PERMISSION / forbidden.no.auth / forbidden.accessDenied and the
  role-threshold wording). Permission failures from other products keep
  their product-specific suggestion (e.g. the mail mailbox hint) or fall
  back to a product-neutral hint instead of being told to run document
  permission commands that cannot fix their problem.

- Null rendering: the null->{} adaptation is limited to the four tools
  with a confirmed empty-response-means-success contract
  (update_permission / remove_permission / update_member /
  remove_member). Every other tool keeps its raw null output so the
  shared machine-output contract stays unchanged.

Update tests and the changelog fragment accordingly; changed-code
coverage stays at 100%.
2026-08-23 16:16:34 +08:00
zengyouling.zyl 8a4e49dbf2 test(helpers): cover permission update/remove members and blank --users branches to #1085 2026-08-23 14:34:17 +08:00
zengyouling.zyl 7c924c54ca fix(drive,doc,wiki): register limit mapping exclusion instead of property redirect to #1085
The server rejects the legacy maxResults path; the CLI now validates
--limit (1-50) and sends it as pageSize at runtime. Schema-compat
rejects a non-empty property redirect (maxResults -> pageSize), so
declare --limit as a CLI pagination input via the reviewed mapping
exclusion ledger (property omitted, provenance
reviewed_mapping_exclusion) on doc.list_permission,
drive.list_permission, and wiki.list_member.
2026-08-23 03:04:11 +08:00
zengyouling.zyl 59d0b6dd75 Merge remote-tracking branch 'upstream/main' into feat/drive-permission-pagination 2026-08-23 02:52:58 +08:00
zengyouling.zyl 74f7bbc980 docs(changes): correct release fragment PR reference to #1085 2026-08-23 02:38:43 +08:00
zengyouling.zyl 2b7d5a2c5f fix(drive,doc,wiki): permission notify default, error guidance, pagination contract to #1065
- --notify now defaults to false and is omitted from the server request
  unless passed explicitly (help updated accordingly)
- forbidden.accessDenied / permission-denied bodies classify as
  AUTH_PERMISSION_DENIED with apply-permission guidance
- user/member validation failures intercepted before RESOURCE_NOT_FOUND
  with --members corpId suggestion
- business error display appends backend code/logId for traceability;
  literal null tool responses render as {}
- drive/doc permission list + wiki member list declare cursor pagination
  (next-token) in Contract; cobra.NoArgs hardening on permission leaves
- cross-platform coverage tests and release fragments updated
2026-08-23 02:25:57 +08:00
github-actions[bot] fcfead71cb Merge pull request #1082 from DingTalk-Real-AI/codex/shortcut-sheet-whiteboard-markdown
feat(shortcuts): harden Sheet Whiteboard and Markdown routes
2026-08-23 01:14:11 +08:00
Dennis 0beb1c6b0c fix(whiteboard): compare readback numbers exactly 2026-08-23 00:54:46 +08:00
Dennis 3b38d4c8da docs(whiteboard): fix shortcut file source syntax 2026-08-23 00:30:58 +08:00
Dennis d68e340a5b fix(whiteboard): preserve interactive confirmation in examples 2026-08-23 00:30:56 +08:00
Dennis 98799effba fix(shortcuts): close sheet and whiteboard review gaps 2026-08-23 00:30:54 +08:00
Dennis 9239f9070a test(ci): share shortcut schema boundary fixture 2026-08-23 00:30:52 +08:00
Dennis 202c5ce697 feat(shortcuts): harden Sheet Whiteboard and Markdown routes 2026-08-23 00:30:49 +08:00
github-actions[bot] 8ab2ac5e7c Merge pull request #994 from FloralTide/codex/fix-event-shutdown-lifecycle
fix(event): clean up shutdown lifecycle
2026-08-21 19:24:44 +08:00
炳昱 b85a342e9f fix(npm): preserve interactive terminal ownership 2026-08-21 19:09:59 +08:00
炳昱 ad72cf4b3d fix(npm): signal the vendor process group 2026-08-21 18:15:19 +08:00
炳昱 89154b3952 fix(npm): avoid duplicate terminal signals 2026-08-21 17:39:48 +08:00
炳昱 b01febf52e Merge remote-tracking branch 'official-upstream/main' into codex/fix-event-shutdown-lifecycle 2026-08-21 17:23:25 +08:00
github-actions[bot] 74b7690cbb Merge pull request #1078 from liyuan333/feat/doc-read-public-and-history-version
feat(doc): read password-protected public docs and historical versions
2026-08-21 17:19:39 +08:00
liyuan333 8312c4f30e Merge branch 'main' into feat/doc-read-public-and-history-version 2026-08-21 16:50:25 +08:00
赤川 35c6fd95e1 Merge pull request #1092 from DingTalk-Real-AI/codex/add-secondary-dingtalk-webhook
ci: notify a secondary DingTalk webhook
2026-08-21 16:24:19 +08:00
chichuan 564ff8563f ci: notify a secondary DingTalk webhook 2026-08-21 16:22:46 +08:00
陌渊 c7510cd1a1 fix(datasource): trim whitespace from batch IDs and fix result/processCode docs
- Add trimNonEmpty for --table-ids in +datasource-sync and --task-ids in
  +datasource-sync-status, matching the existing field-ids pattern
- Add 4 test cases: whitespace-only rejection and trim-through for both
- Fix usage guide: typical workflow and notes no longer equate result
  with processCode; correctly describe result as JSON to parse for
  approvals[].processCode/name/iconUrl/url
2026-08-21 16:19:43 +08:00
john 1c3477c087 Merge branch 'main' into feat/drive-permission-pagination 2026-08-21 16:18:15 +08:00
陌渊 93d450a9bf fix(datasource): read --field-ids as string slice, not string
--field-ids is declared as FlagStringSlice, but DatasourceCreate and
DatasourceUpdate previously called rt.Str to check whether the flag
was empty. RuntimeContext.Str delegates to cobra's GetString, which
returns an empty string on slice-typed flags, so the empty-value
guard rejected every explicit --field-ids input and the downstream
MCP tool never received fieldIds.

Switch to rt.StrSlice, sanitize through a new trimNonEmpty helper
(drop whitespace-only / empty entries) and pass the cleaned slice
to MCP. Add success-passthrough tests for both create and update,
plus a whitespace-only rejection case, and enhance the mock caller
to record MCP arguments so fieldIds can be asserted.
2026-08-21 16:10:23 +08:00
陌渊 cf39768095 fix(datasource): align field-ids semantics and test naming for coverage gate
- Update --field-ids description in create/update shortcuts and the
  helper-layer datasource update to clarify that omitting the flag
  keeps existing config (create defaults to all fields), matching the
  actual update overwrite semantics.
- Rename datasource shortcut coverage tests to the
  TestCrossPlatformCoverage* prefix so they are picked up by the
  macOS platform coverage gate.
2026-08-21 16:10:20 +08:00
陌渊 c096258b0f fix(datasource): reject empty field-ids and auto-sync-setting in shortcut layer
Align shortcut layer validation with helper layer to prevent empty slices
from being sent to MCP, which could clear sync field selection due to
datasource update's overwrite semantics.

- Add empty string checks for --field-ids in both create and update shortcuts
- Add empty string checks for --auto-sync-setting in both create and update shortcuts
- Add regression tests verifying MCP is not called when empty values are rejected
- Both public entry points now have consistent validation behavior

Fixes P1 auto-CR issue for empty flag bypass vulnerability.
2026-08-21 16:10:18 +08:00
陌渊 5ba8ac6775 test(aitable): cover datasource shortcut and helper error paths for 100% changed-code coverage 2026-08-21 16:10:15 +08:00
陌渊 66fee5ef5f fix(aitable): update shortcut counts after rebase onto upstream main 2026-08-21 16:10:12 +08:00
陌渊 d9b9c5c7da fix(aitable): reject empty field-ids/auto-sync-setting and non-object JSON 2026-08-21 16:10:09 +08:00
陌渊 684411e54e fix(aitable): require task-ids for datasource sync-status and align docs
Make +datasource-sync-status consistent across shortcut and native
commands: --task-ids is now required, descriptions focus on querying
by taskId, and optional/IDLE semantics are removed. Update tests,
usage guide, reference doc, and SKILL description accordingly.
2026-08-21 16:10:05 +08:00
陌渊 de5ba029d4 fix(aitable): add field-ids/auto-sync-setting to native datasource create/update
Native datasource create/update now expose --field-ids and
--auto-sync-setting, matching the shortcut-layer capabilities:
- flags registered on both commands
- Contract Parameters updated
- values mapped to MCP tool args
- JSON validation for --auto-sync-setting
- no-change update guard now counts the new flags

Also fixes the missing required name in the usage-guide update example.
2026-08-21 16:10:02 +08:00
陌渊 15f139e32d fix(aitable): reject no-change datasource update and fix doc example
+datasource-update now requires at least one mutable option
(--source-config, --auto, --field-ids, or --auto-sync-setting)
before calling update_datasource_config, preventing accidental
sync triggers. The native datasource update command enforces the
same guard for its supported flags. Also adds the required name
field to the +datasource-get-fields doc example.
2026-08-21 16:09:58 +08:00
陌渊 768c1ce494 fix(aitable): only send --auto on datasource-update when explicitly set
Omitting --auto on +datasource-update previously sent auto=false to
MCP, silently disabling auto-sync for existing datasources. Now auto
is only included in tool args when the flag is explicitly provided,
so --auto=true and --auto=false work while omission preserves the
existing setting. Updated flag descriptions and added tests.
2026-08-21 16:09:55 +08:00
陌渊 966fd60e2f fix(aitable): always send auto=false for datasource create/update
MCP requires the auto field in create_datasource / update_datasource_config
requests. Previously CLI only sent it when --auto was explicitly changed,
causing failures when users omitted the flag. Now both shortcut and helper
layers always include auto=false by default.

Also update flag descriptions and docs to clarify that the field is always
sent downstream, and add test assertions for the default-false behavior.
2026-08-21 16:09:51 +08:00
陌渊 7825c3c7e0 docs(aitable): fix datasource doc inconsistencies for auto CR P2
- docs/datasource-usage-guide.md: clarify that list-sources result is a
  JSON string containing approvals[]; add missing --auto-sync-setting
  parameter table rows and a dedicated autoSyncSetting format section
  using the correct scheduled/daily/weekly/monthly enums.
- skills/references/aitable/aitable-datasource.md: fix autoSyncSetting
  enums (schedule/day/week/month -> scheduled/daily/weekly/monthly) and
  update the create example accordingly.
2026-08-21 16:09:49 +08:00
陌渊 10d44615d1 fix(aitable): include required name in datasource source-config examples
The OA approval source-config contract requires processCode, name,
iconUrl, and url to be passed through unchanged from +datasource-list-sources.
Published examples for +datasource-create, +datasource-update, and
+datasource-get-fields were missing `name`, and the usage guide marked it
as optional. Fix all examples in the shortcut layer, helper layer, and
docs; update flag descriptions to mention name; and add a contract test
that validates every delivered example's source-config JSON contains the
required members.
2026-08-21 16:09:39 +08:00
陌渊 1e88612e43 test(aitable): add datasource helper tests for 100% changed-code coverage
21 tests covering all 7 datasource leaf commands' error paths (missing
required flags, count validation) and happy paths (source-config as raw
string, --auto flag, boundary cases for table-ids/task-ids).
2026-08-21 16:09:36 +08:00
陌渊 5934ccac7f fix(aitable): enforce 1-5 count limit on table-ids and task-ids
Both the shortcut (+datasource-sync, +datasource-sync-status) and
helper (datasource sync, datasource sync-status) layers now validate
that table-ids contains 1-5 IDs and task-ids contains at most 5 IDs
before calling MCP, matching the declared contract.
2026-08-21 16:09:30 +08:00
陌渊 d9365f3fff docs: remove unimplemented --conflict-strategy from all datasource docs 2026-08-21 16:09:27 +08:00
陌渊 15d93698bd fix(aitable): use String instead of StringSlice for datasource flags
ValidateRequiredFlags calls GetString which returns empty for
StringSlice flags, causing the examples test to report --table-ids
as missing. Switch to String + parseCSVValues to match the codebase
convention used by record-ids and other comma-separated flags.
2026-08-21 16:09:23 +08:00
陌渊 7e69a3d6fe fix(aitable): add datasource helper leaf commands and fix CI test counts
- Add 7 datasource leaf commands to internal/helpers/aitable.go so
  coverage test can find tool name literals (fixes TestAllShortcutsAssemble)
- Add 7 entries to semantic_catalog_aitable.json and update catalog count
  from 93 to 100 (fixes TestCrossPlatformCoverageAITableSemanticCatalog)
- Update publicShortcutCount/schemaPublishedShortcutCount/publiclyDelivered
  from 422/447/422 to 429/454/429 (fixes TestDeliverySchemaCoversOrExactly)
- Fix Contract.Selection.AgentSummary and UseWhen[0] in datasource.go to
  match Description and Intent exactly as required by schema contract test
2026-08-21 16:09:21 +08:00
陌渊 94b4958038 chore(aitable): regenerate SKILL.md shortcut section via gen_skill_shortcut_sections.py 2026-08-21 16:09:19 +08:00
陌渊 97a99ba04f style: fix gofmt indentation in datasource.go 2026-08-21 16:09:17 +08:00
陌渊 5e1e5cbb84 fix(aitable): remove duplicate datasource-get-fields and datasource-list-sources rows in SKILL.md 2026-08-21 16:09:14 +08:00
陌渊 e04e886c50 chore: add release fragment for aitable datasource shortcuts 2026-08-21 16:09:12 +08:00
陌渊 1acde9b766 feat(aitable): align datasource shortcuts with MCP snapshot [WP-40-006]
- Fix autoSyncSetting enum: scheduled/daily/weekly/monthly; mark
  selectedMonthDays/selectedWeekdays as required for monthly/weekly
- Remove splitParentTableField from --source-config user-settable fields;
  add note that splitParentTableField/enableDataSyncOaDetailList are
  internal downstream fields not to be passed
- Prepend sync-is-fire-and-forget notice to DatasourceSync descriptions
- Remove --conflict-strategy flag (syncConflictStrategy not in MCP schema)
2026-08-21 16:09:09 +08:00
陌渊 852efe56aa feat(aitable): add datasource skill optimization
- Golden Route: add datasource entry (list-sources → create flow)
- 常用 leaf 直达: add datasource-* commands
- 当前最短路径: add list-sources-first rule
- 安全边界: add sync write warning
- 错误最短路径: add errorCode=4014 and sync=false handling
- 按需加载: add datasource reference trigger
- New reference: aitable-datasource.md with full workflow, sourceConfig
  protocol, autoSyncSetting config, command details, error codes
2026-08-21 16:09:05 +08:00
陌渊 6c287bcb4d feat(aitable): align datasource shortcuts with MCP snapshot [WP-40-005]
- Add --auto-sync-setting flag to DatasourceCreate (was only in Execute, not in Flags)
- Expand DatasourceSync description: add 文档链接, errorCode=4014 幂等冲突, 非数据源表参数错误
- Simplify DatasourceGetFields description: remove field property enumeration to match snapshot
2026-08-21 16:09:00 +08:00
陌渊 df24d53886 feat(aitable): align datasource shortcuts with MCP snapshot [WP-40-004]
Sync CLI field descriptions with latest ai-table-mcp-snapshot.json:
- source-config flags: restructure to "两类字段" (4 passthrough + caller-set),
  add splitParentTableField, fix Update flag to optional semantics
- get_datasource_sync_status: update status list (RUNNING/FINISHED/FAILED,
  remove TIMEOUT), change "不传返回最近一次" → "IDLE(下游暂不支持)"
- get_datasource_config: add sync=true guard note, "其他类型暂不支持", sourceConfig hint
- list_datasource_sources: full rewrite explaining result/approvals structure,
  4-field passthrough rule, enableDataSyncOaDetailList internal note
- get_datasource_fields: add "其他数据源类型暂不支持,待后续开放"
2026-08-21 16:08:50 +08:00
陌渊 49cecabb12 [WP-40-003] feat: align 7 datasource shortcuts with latest MCP snapshot
- Add --auto-sync-setting flag (JSON string) to +datasource-create and
  +datasource-update, validated and passed through as raw string.
- Update +datasource-update --source-config desc to reflect full
  replacement semantics ("传入时整体覆盖") and spell out required /
  optional fields with defaults.
- Append "仅支持 OA 审批数据源 (datasourceType=OA)" to
  +datasource-get-config description.
- Simplify +datasource-list-sources / +datasource-get-fields
  descriptions to concise Chinese aligned with snapshot wording.
- Update SKILL.md shortcuts table and add datasource usage guide.
2026-08-21 16:08:45 +08:00
陌渊 09993ad82c [WP-40-002] fix: correct idempotency value from not_idempotent to non_idempotent 2026-08-21 16:08:42 +08:00
陌渊 0efaf6c82f [WP-40-002] feat: update SKILL.md with 5 datasource shortcuts and trigger words 2026-08-21 16:08:39 +08:00
陌渊 26002637f4 [WP-40-001] feat: add 5 datasource shortcuts for aitable
Add 5 data source sync management shortcuts to the aitable service:
- +datasource-create (create_datasource): create sync config + first sync
- +datasource-update (update_datasource_config): update existing sync config
- +datasource-sync (run_datasource_sync): trigger manual sync (max 5 tables)
- +datasource-sync-status (get_datasource_sync_status): query sync task status
- +datasource-get-config (get_datasource_config): get sync config details

Each shortcut declares a full Contract (Identity/Interface/Selection),
Safety, Flags, and Execute that calls rt.CallMCPData on the "aitable"
MCP server. datasource-type is passed through without CLI enum check;
source-config is validated as a JSON object via parseJSONObject.
2026-08-21 16:08:35 +08:00
github-actions[bot] f7229091ae Merge pull request #1053 from anxiangbo/feat/20260817_agoal_search
Feat/20260817 agoal search
2026-08-21 07:50:26 +00:00
liyuan333 77aa813467 Merge branch 'main' into feat/doc-read-public-and-history-version 2026-08-21 15:42:53 +08:00
anxiangbo 1f595571c0 Merge branch 'main' into feat/20260817_agoal_search 2026-08-21 15:27:26 +08:00
github-actions[bot] cd90d1c322 Merge pull request #1075 from Justper/oa_attachment_upload_dws
Oa attachment upload dws
2026-08-21 14:46:26 +08:00
liyuan 49ab53ea0d 评审问题修复 2026-08-21 14:29:05 +08:00
昭逸 78433198fb Merge branch 'oa_attachment_upload_dws' of github.com:Justper/dingtalk-workspace-cli into oa_attachment_upload_dws
to #666
2026-08-21 14:23:27 +08:00
昭逸 4863152a4a Merge remote-tracking branch 'upstream/main' into oa_attachment_upload_dws
to #666
2026-08-21 14:20:33 +08:00
昭逸 2057fec3b0 fix(oa): normalize spaceId/fileSize to match ResultSpec integer declaration to #666
- validateOAAttachmentCommitResult 改为返回归一化后的 result map
- string 型 spaceId 通过 ParseInt 转 int64,json.Number 同理,非法字符串报错
- fileSize 的 json.Number 同样归一化为 int64
- 新增归一化行为测试 + 输出契约测试,覆盖率 100% to #666
2026-08-21 14:19:53 +08:00
anxiangbo 1308d08862 Merge branch 'DingTalk-Real-AI:main' into feat/20260817_agoal_search 2026-08-21 14:00:30 +08:00
github-actions[bot] 8b56e9bc9e Merge pull request #1073 from maoqxxmm/codex/sheet-revision-changeset
feat(sheet): add revision and changeset inspection
2026-08-21 05:56:10 +00:00
毛球 87e141f2de Merge branch 'main' into codex/sheet-revision-changeset 2026-08-21 13:38:56 +08:00
github-actions[bot] 9b521f0392 chore: update beta formula for v1.0.60-beta.1 [skip ci] 2026-08-21 05:17:50 +00:00
YanChangzhi 4dcd528bc1 Merge branch 'main' into oa_attachment_upload_dws 2026-08-21 13:11:34 +08:00
赤川 0bbb3a9d32 Merge pull request #1087 from DingTalk-Real-AI/codex/changelog-v1.0.60-beta.1
chore: prepare v1.0.60-beta.1 changelog
2026-08-21 12:48:22 +08:00
chichuan 0ebd840ba9 chore: prepare v1.0.60-beta.1 changelog 2026-08-21 12:35:38 +08:00
github-actions[bot] 9d356cd664 Merge pull request #1076 from hlzjsong/refresh_org_slot_fix
refresh org slot not only identity
2026-08-21 04:20:48 +00:00
YanChangzhi b00f43ee06 Merge branch 'main' into oa_attachment_upload_dws 2026-08-21 12:14:19 +08:00
赤川 23167ef974 Merge branch 'main' into refresh_org_slot_fix 2026-08-21 11:46:45 +08:00
毛球 8b003aef16 Merge branch 'main' into codex/sheet-revision-changeset 2026-08-21 11:43:45 +08:00
github-actions[bot] 11934eed05 Merge pull request #1081 from DingTalk-Real-AI/codex/fix-report-requiredness-governance
feat(policy): govern optional-to-required flag migrations
2026-08-21 11:40:11 +08:00
玉澜 d552c59d11 feat(drive,doc,wiki): permission/member list pagination and multi-type members
Sync the permission CRUD overhaul from the internal CLI (MR 28965577):

- drive/doc permission list and wiki member list now accept --next-token
  to follow the server cursor (totalCount/hasMore/nextToken); --limit maps
  to pageSize capped at 50 instead of the rejected maxResults=200 path
  (fixes #1065)
- permission add/update/remove and wiki member add/update/remove accept a
  --members JSON array (USER/DEPT/CONVERSATION/TAG grantee types, each with
  its own roleId) with optional --notify; legacy --users/--role stays
- cursor/page-token hidden cross-product aliases now resolve to next-token
- regenerate param_aliases_generated.go; wiki member list override no
  longer blocks cursor
- update mono/multi skill references and add change fragment
2026-08-21 11:30:22 +08:00
YanChangzhi 23e1085a37 Merge branch 'main' into oa_attachment_upload_dws 2026-08-21 11:16:29 +08:00
赤川 a352615e77 Merge branch 'main' into refresh_org_slot_fix 2026-08-21 11:15:06 +08:00
xiatian d210da501a Merge remote-tracking branch 'upstream/main' into codex/sheet-revision-changeset 2026-08-21 11:14:03 +08:00
赤川 6288199a93 Merge branch 'main' into codex/fix-report-requiredness-governance 2026-08-21 11:05:33 +08:00
anxiangbo 6d9781fe15 Merge branch 'main' into feat/20260817_agoal_search 2026-08-21 11:04:19 +08:00
赤川 5b34ed1a7e Merge pull request #1084 from DingTalk-Real-AI/codex/docs-dws-cli-open
docs: 公告 DWS CLI 全面开放
2026-08-21 11:02:36 +08:00
chichuan 3d9a469347 docs: announce DWS CLI availability 2026-08-21 11:01:08 +08:00
xiatian 7640ba7614 fix(sheet): validate changeset audit integrity 2026-08-21 10:59:14 +08:00
anxiangbo c790fe3c3b Merge branch 'main' into feat/20260817_agoal_search 2026-08-21 10:44:53 +08:00
昭逸 4886bdb3f5 Merge remote-tracking branch 'upstream/main' into oa_attachment_upload_dws
to #666
2026-08-21 10:41:06 +08:00
昭逸 4aef07ccd3 fix(oa): validate commit response required fields before reporting success to #666
- 新增 validateOAAttachmentCommitResult 校验 spaceId/fileName/fileSize/fileId 必需字段
- commit 步不再使用通用 callOAAttachmentResultCtx,改为专用校验后才存储成功结果
- 补充 malformed commit 响应回归测试,覆盖率 100%
2026-08-21 10:40:46 +08:00
github-actions[bot] e0c49377d6 Merge pull request #1074 from DingTalk-Real-AI/codex/investigate-calendar-todo-comment-regressions
fix: harden calendar todo and comment shortcuts
2026-08-21 10:30:04 +08:00
昭逸 dc37dd2c34 fix(oa): remove DDAttachment from unsupported table and use testseam.Swap to #666
- 从 oa-form-components.md (mono/multi) 的"API 不支持的控件"表中移除 DDAttachment,避免 Agent 误判为不支持
- computeFileMD5 测试注入改为 testseam.Swap,符合仓库包变量注入约定
2026-08-21 09:56:48 +08:00
hlzjsong 02aad9020a Merge branch 'main' into refresh_org_slot_fix 2026-08-21 09:26:39 +08:00
昭逸 d59d1091dc Merge remote-tracking branch 'upstream/main' into oa_attachment_upload_dws
to #666
2026-08-21 08:51:13 +08:00
昭逸 60d6bfeec4 Merge branch 'oa_attachment_upload_dws' of github.com:Justper/dingtalk-workspace-cli into oa_attachment_upload_dws
to #666
2026-08-21 08:50:28 +08:00
昭逸 bf89acb3d2 fix ci fail to #666 2026-08-21 08:50:13 +08:00
Dennis 35d6f47bd6 Merge remote-tracking branch 'origin/main' into codex/investigate-calendar-todo-comment-regressions 2026-08-21 08:17:52 +08:00
xiatian d24b71614a Merge remote-tracking branch 'upstream/main' into codex/sheet-revision-changeset 2026-08-21 01:23:17 +08:00
github-actions[bot] 765b961f4d Merge pull request #1071 from DingTalk-Real-AI/codex/fix-stable-active-fragments
fix(release): consume post-beta fragments in stable seals
2026-08-21 01:06:18 +08:00
xiatian 9ab4bd10a5 Merge remote-tracking branch 'upstream/main' into codex/sheet-revision-changeset 2026-08-21 01:05:47 +08:00
chichuan 14c5bed4fc ci: split app-c race partition for runner headroom 2026-08-21 00:50:46 +08:00
赤川 c1bd6dcf64 Merge branch 'main' into codex/fix-stable-active-fragments 2026-08-21 00:06:27 +08:00
Dennis 1c8b83ec2f Merge remote-tracking branch 'origin/main' into codex/investigate-calendar-todo-comment-regressions 2026-08-20 23:58:13 +08:00
Dennis bb6f470df5 test: address shortcut regression review 2026-08-20 23:56:08 +08:00
赤川 01af71a5ae Merge branch 'main' into oa_attachment_upload_dws 2026-08-20 23:47:06 +08:00
github-actions[bot] d4ff8a5f4f Merge pull request #1070 from DingTalk-Real-AI/codex/oa-ding-report-shortcuts
feat(shortcut): harden OA DING and Report workflows
2026-08-20 23:44:23 +08:00
赤川 47e3c2b3c5 Merge branch 'main' into refresh_org_slot_fix 2026-08-20 23:19:33 +08:00
xiatian e8ecff586a fix(sheet): classify malformed revision responses 2026-08-20 23:06:56 +08:00
Dennis 11a9ad5d49 fix(shortcut): align OA execution availability 2026-08-20 23:03:35 +08:00
Dennis 23940e4752 revert: keep coverage shard output compact 2026-08-20 22:24:17 +08:00
Dennis 8cb0f64477 fix(shortcut): reject backward OA cursors 2026-08-20 22:15:27 +08:00
Dennis bbaf033618 ci: stream app coverage progress 2026-08-20 22:14:35 +08:00
xiatian 57cca7ef71 fix(sheet): validate revision result contracts 2026-08-20 22:02:26 +08:00
Dennis 2d38beb7be fix(shortcuts): separate compatibility visibility from availability 2026-08-20 21:51:21 +08:00
昭逸 4372a8c5ba Merge remote-tracking branch 'upstream/main' into oa_attachment_upload_dws
to #666
2026-08-20 21:44:16 +08:00
昭逸 422dc0fde3 fix ci fail to #666 2026-08-20 21:44:05 +08:00
muling.cs 3d59411a1a refresh slot repair org 2026-08-20 21:24:06 +08:00
chichuan fe66ac18a4 feat(policy): govern flag requiredness changes 2026-08-20 21:19:55 +08:00
Dennis bda408d966 test(ding): cover compatibility reminder mappings 2026-08-20 21:10:45 +08:00
Dennis 33631502c9 fix(shortcuts): align unavailable writes and query validation 2026-08-20 21:02:52 +08:00
毛球 f2a608d146 Merge branch 'main' into codex/sheet-revision-changeset 2026-08-20 20:44:17 +08:00
Dennis 378b9f67a2 Merge remote-tracking branch 'origin/main' into codex/investigate-calendar-todo-comment-regressions 2026-08-20 20:41:56 +08:00
xiatian 43ba466783 fix(sheet): require fresh confirmation for version revert 2026-08-20 20:28:35 +08:00
Dennis 5c9f738012 fix(shortcuts): preserve published schema bindings 2026-08-20 20:20:41 +08:00
Dennis 56c5fb35b8 chore(policy): retire completed flag migrations 2026-08-20 20:20:27 +08:00
Dennis b19c52f61b fix(oa): make approval keyword normalization explicit 2026-08-20 20:20:25 +08:00
Dennis c0641dbf64 fix(shortcut): preserve OA and DING CLI compatibility 2026-08-20 20:20:23 +08:00
Dennis 3b5cb3b0cb test(shortcut): close OA DING Report coverage gaps 2026-08-20 20:20:21 +08:00
Dennis fd2ed2174f chore(release): add OA DING Report fragment 2026-08-20 20:20:19 +08:00
Dennis 5bbaa304e3 docs(shortcut): refresh OA DING Report live evidence 2026-08-20 20:20:17 +08:00
Dennis db938778af chore(shortcut): sync OA DING Report with current main 2026-08-20 20:20:15 +08:00
Dennis 1155b9b5c0 test(shortcut): align OA reviewed input fixtures 2026-08-20 20:20:12 +08:00
Dennis 8fa93ef030 fix(shortcut): retire OA discovery aliases after downgrade 2026-08-20 20:20:10 +08:00
Dennis f4ad1a15f5 docs(shortcut): record Report double-layer release proof 2026-08-20 20:20:08 +08:00
Dennis d2cbd928e2 docs(shortcut): record DING double-layer release proof 2026-08-20 20:20:06 +08:00
Dennis 2346dfba4e fix(shortcut): require OA zero-page pagination evidence 2026-08-20 20:20:03 +08:00
Dennis f6ad2fa01a fix(shortcut): publish Report range constraints 2026-08-20 20:19:44 +08:00
Dennis 7bc56f3dea feat(shortcut): unlock Report outbox workflows 2026-08-20 20:19:41 +08:00
Dennis 03001eb4e0 fix(shortcut): harden DING write routing evidence 2026-08-20 20:19:39 +08:00
Dennis 91974ce981 docs(shortcut): close OA residual audit gaps 2026-08-20 20:19:37 +08:00
Dennis c6417e3527 feat(shortcut): harden Report reads and availability 2026-08-20 20:19:35 +08:00
Dennis 161687ae4c fix(shortcut): deliver OA validation evidence 2026-08-20 20:19:32 +08:00
Dennis 4da5a07d1d feat(shortcut): harden DING reads and availability 2026-08-20 20:19:30 +08:00
Dennis 2cb83d388b fix(shortcut): publish OA validation constraints 2026-08-20 20:19:25 +08:00
Dennis ba9c0f624e feat(shortcut): harden OA workflows and availability 2026-08-20 20:19:19 +08:00
Dennis ff65f80c98 refactor(oa): add strict shortcut response helpers 2026-08-20 20:19:09 +08:00
github-actions[bot] 42240f5e9e Merge pull request #1079 from DingTalk-Real-AI/codex/fix-stable-migration-receipts
fix(ci): keep completed migration receipts inert
2026-08-20 20:18:04 +08:00
Dennis e6b06b561d Merge remote-tracking branch 'origin/main' into codex/investigate-calendar-todo-comment-regressions 2026-08-20 19:50:43 +08:00
chichuan 11cbc30a10 fix(ci): keep completed migration receipts inert 2026-08-20 19:16:07 +08:00
xiatian 60a474f30c fix(sheet): fail closed on invalid revision results 2026-08-20 19:15:48 +08:00
xiatian 6e8fec5684 chore(policy): retire consumed flag migrations 2026-08-20 17:48:59 +08:00
liyuan 470aa42d7b chore: keep release note in .changes fragment, restore CHANGELOG.md 2026-08-20 17:35:03 +08:00
liyuan a74d96bb96 feat(doc): read password-protected public docs and historical versions 2026-08-20 17:32:11 +08:00
liyuan 9798a60728 feat(doc): read password-protected public docs and historical versions 2026-08-20 17:13:46 +08:00
毛球 e028d443d5 Merge branch 'main' into codex/sheet-revision-changeset 2026-08-20 17:11:29 +08:00
chichuan 74859b966b fix(release): consume active fragments in stable seals 2026-08-20 17:07:53 +08:00
xiatian 76a5559ca2 fix(sheet): align revision dry-run contract 2026-08-20 16:59:18 +08:00
昭逸 c4a1018213 删除无关文件 to #666 2026-08-20 16:55:18 +08:00
github-actions[bot] 62d72ad84c chore: update formula for v1.0.59 [skip ci] 2026-08-20 08:45:55 +00:00
Dennis e6fe475aea chore: retire consumed chat flag migration 2026-08-20 16:44:07 +08:00
muling.cs e95ac52ff4 refresh org slot not only identity 2026-08-20 16:41:35 +08:00
Dennis 61f8140f91 test: close shortcut regression coverage gaps 2026-08-20 16:38:05 +08:00
昭逸 09c0cd7849 merge uptream to #666 2026-08-20 16:26:32 +08:00
xiatian 5a368a9ab8 Merge remote-tracking branch 'upstream/main' into codex/sheet-revision-changeset 2026-08-20 16:11:37 +08:00
Dennis fbcd8887ee fix: harden calendar todo and comment shortcuts 2026-08-20 16:09:18 +08:00
赤川 c0838e7e41 Merge pull request #1072 from DingTalk-Real-AI/codex/changelog-v1.0.59-stable
chore: prepare v1.0.59 changelog
2026-08-20 16:06:54 +08:00
chichuan 9c6ab99bf1 chore: prepare v1.0.59 changelog 2026-08-20 16:01:09 +08:00
github-actions[bot] 87ab311764 chore: update beta formula for v1.0.59-beta.5 [skip ci] 2026-08-20 07:55:40 +00:00
昭逸 7f4318a10d fix审批skill中附件描述 to #666 2026-08-20 15:39:14 +08:00
xiatian 5232f632c8 Merge remote-tracking branch 'upstream/main' into codex/sheet-revision-changeset 2026-08-20 15:35:19 +08:00
xiatian 615a775fdf feat(sheet): add revision changeset inspection 2026-08-20 15:34:45 +08:00
anxiangbo b10da77e09 Merge branch 'main' into feat/20260817_agoal_search 2026-08-20 15:22:19 +08:00
昭逸 cefc5c005c 附件上传dws合并为一个 to #666 2026-08-20 15:14:39 +08:00
赤川 15c075e6a6 Merge pull request #1068 from DingTalk-Real-AI/codex/changelog-v1.0.59-beta.5
chore: prepare v1.0.59-beta.5 changelog
2026-08-20 14:56:06 +08:00
chichuan f6d1e685e0 chore: prepare v1.0.59-beta.5 changelog 2026-08-20 14:52:30 +08:00
github-actions[bot] 81108e150b Merge pull request #1046 from xlb1130/feat/85614588-chat-personal-emotion
feat(chat): add personal emotion commands
2026-08-20 06:27:50 +00:00
xlb1130 dad9aefefa Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-20 14:08:05 +08:00
github-actions[bot] 71d49cb12b Merge pull request #1033 from pengzhihan47-star/codex/dingtalk-doc-skill-opt-v1
docs(skill): optimize dingtalk-doc workflows
2026-08-20 14:04:45 +08:00
柏智 f7e2efaaa2 ci: retrigger checks 2026-08-20 13:50:18 +08:00
pengzhihan47-star 557208e16b Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 13:31:31 +08:00
xlb1130 53401dbb0c Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-20 13:25:41 +08:00
github-actions[bot] 6c52ac37dd Merge pull request #1066 from DingTalk-Real-AI/codex/minutes-todo-wiki-param-aliases
feat(cli): expand Minutes TODO Wiki parameter aliases
2026-08-20 13:21:38 +08:00
xlb1130 95a17a3ffc Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-20 13:21:07 +08:00
pengzhihan47-star 3318741508 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 13:01:04 +08:00
克谨 3d7ab2690c feat(cli): expand Minutes TODO Wiki parameter aliases 2026-08-20 12:45:19 +08:00
github-actions[bot] 17eefcd24b Merge pull request #1064 from DingTalk-Real-AI/codex/fix-1060-schema-lineage
fix(policy): preserve historical Schema migration lineage
2026-08-20 04:29:42 +00:00
xlb1130 6f62ce7997 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-20 12:17:24 +08:00
赤川 2228a32d1a Merge branch 'main' into codex/fix-1060-schema-lineage 2026-08-20 12:11:55 +08:00
github-actions[bot] a6f79e951b Merge pull request #1050 from DingTalk-Real-AI/codex/fix-cli-eval-functional
fix: harden shortcut functional workflows
2026-08-20 04:08:39 +00:00
长真 096dfd48f0 docs(chat): keep emotion skill route within budget 2026-08-20 11:57:55 +08:00
chichuan 3922970bfc fix(policy): preserve schema migration lineage 2026-08-20 11:52:00 +08:00
xlb1130 2ab0edd5c6 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-20 11:45:41 +08:00
pengzhihan47-star 4b3272bcd4 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 11:42:41 +08:00
长真 80d5d24637 Revert "docs(chat): trim chat skill context budget"
This reverts commit c5951a10ff.
2026-08-20 11:39:50 +08:00
柏智 e40397e239 docs(skill): restore bounded doc guidance 2026-08-20 11:34:14 +08:00
xlb1130 15bc7fdc3f Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-20 11:27:40 +08:00
柏智 da049be58d docs(skill): require terminal evidence for doc writes 2026-08-20 11:21:58 +08:00
柏智 6ec64e8a03 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 11:09:24 +08:00
柏智 bca56cbba6 Merge remote-tracking branch 'origin/codex/dingtalk-doc-skill-opt-v1' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 11:09:19 +08:00
昭逸 103b05413e 审批附件相关dws help补充 to #666 2026-08-20 10:40:35 +08:00
john bb48aa0cc8 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 10:37:44 +08:00
柏智 6ffb4bcb93 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 10:37:30 +08:00
昭逸 169bbe88c0 上传附件dws to #666 2026-08-20 10:24:35 +08:00
pengzhihan47-star 08595594d7 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 10:15:26 +08:00
anxiangbo 30782020ad Merge branch 'main' into feat/20260817_agoal_search 2026-08-20 10:03:21 +08:00
柏智 540bbac35b Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 09:57:23 +08:00
柏智 c4d5595ca9 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 02:09:49 +08:00
柏智 86d1eb8030 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 22:47:50 +08:00
柏智 ab529e5ee5 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 22:05:02 +08:00
xlb1130 15cb1f4311 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-19 22:01:44 +08:00
长真 97ca00868f test(chat): cover personal emotion user resolution 2026-08-19 21:51:49 +08:00
xlb1130 df8885c350 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-19 21:06:35 +08:00
赤川 d87cdef00b Merge branch 'main' into codex/fix-event-shutdown-lifecycle 2026-08-19 20:04:42 +08:00
柏智 4e27a3a84a Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 19:45:13 +08:00
xlb1130 8685464c53 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-19 19:28:52 +08:00
长真 26b5939f9f chore(ci): retrigger pr checks 2026-08-19 18:34:38 +08:00
柏智 95bcace6bd Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 18:10:22 +08:00
长真 ce57cdf260 chore(ci): retrigger pr checks 2026-08-19 16:26:32 +08:00
anxb 999e7a7b9d feat: agoal新增dws2 2026-08-19 15:58:28 +08:00
anxb 94cee4388e Merge remote-tracking branch 'refs/remotes/origin/main' into feat/20260817_agoal_search 2026-08-19 15:38:58 +08:00
xlb1130 3ec138ba99 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-19 15:14:09 +08:00
anxb dcc7e72ec1 feat: agoal新增dws 2026-08-19 15:05:21 +08:00
柏智 f419c0f96d Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 14:37:01 +08:00
xlb1130 17101a8901 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-19 13:41:20 +08:00
柏智 66aa00fb50 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 12:23:42 +08:00
长真 9d6e151a6f Merge remote-tracking branch 'upstream/main' into feat/85614588-chat-personal-emotion 2026-08-19 10:54:30 +08:00
柏智 0df41d3eff Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 10:40:45 +08:00
柏智 2a1ed8cc7a Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 10:09:48 +08:00
长真 c5951a10ff docs(chat): trim chat skill context budget 2026-08-19 00:55:27 +08:00
长真 3dbd29ab50 feat(chat): add personal emotion commands 2026-08-18 23:59:15 +08:00
柏智 1b50c7a5b4 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 23:25:57 +08:00
柏智 cbd70d1b88 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 21:03:52 +08:00
柏智 0ae8949d40 fix(doc): align skill contracts with runtime 2026-08-18 20:53:02 +08:00
pengzhihan47-star 0975d970d1 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 20:36:25 +08:00
柏智 7808673431 fix(doc): align media receipt contract 2026-08-18 20:31:07 +08:00
pengzhihan47-star 50ed921ca1 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 19:28:45 +08:00
pengzhihan47-star b34c29ec35 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 18:41:09 +08:00
pengzhihan47-star 97e5ded043 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 13:49:34 +08:00
pengzhihan47-star 7ceeafbae8 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 12:27:08 +08:00
pengzhihan47-star 54b4a24a14 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 11:35:30 +08:00
pengzhihan47-star e1bfb343f4 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 10:13:27 +08:00
柏智 7da423bf3c docs(skill): clarify import and recent document routes 2026-08-18 09:37:47 +08:00
柏智 fa83ee579c docs(skill): remove lark-specific wording 2026-08-18 08:25:06 +08:00
pengzhihan47-star 25c694aa2a Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 07:46:06 +08:00
柏智 e064d394ba docs(skill): optimize dingtalk doc workflows 2026-08-18 01:02:37 +08:00
炳昱 5a001f33b6 fix(event): clean up shutdown lifecycle 2026-08-13 17:34:20 +08:00
407 changed files with 158309 additions and 5742 deletions
@@ -0,0 +1,6 @@
---
category: Added
---
- **Whiteboard shortcuts** (#1082) — adds strict query and confirmed update workflows with stable-target receipts and exact readback verification.
- **Sheet shortcut hardening** (#1082) — makes worksheet listing and cell-range reads fail closed on malformed, ambiguous, or truncated responses, publishes a closed reviewed output shape, and preserves non-executing `--dry-run` previews for range reads.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **AiSearch and Contact shortcuts** (#1083) — adds strict people search and reviewed unified results; people results must use the live-reviewed `person` source, and exact mobile lookups normalize accepted formatting before calling the dedicated mobile interface. Agent/public discovery keeps `contact +list-roles`, `contact +list-roster-fields`, `contact +get-roster`, and incomplete Live routes unavailable rather than publishing ambiguous results, while the historical Contact CLI commands retain legacy MCP execution and real error propagation. The legacy role-list projection preserves the service's reviewed null placeholder without exposing that ambiguous row through Agent Result contracts.
@@ -0,0 +1,24 @@
---
category: Changed
---
- **Permission error guidance and error rendering** (#1085) —
permission-denied responses now exit with the `AUTH_PERMISSION_DENIED` code
instead of a generic business-error rendering; document/wiki-specific errors
(the drive-specific codes `forbidden.accessDenied` / `forbidden.no.auth`,
or the role-threshold wording like
“需要您具备 MANAGER 及以上角色”) carry apply-permission guidance
(`dws drive permission apply-info` / `dws drive permission apply`), while
permission failures carrying only generic code names (`FORBIDDEN`,
`NO_PERMISSION` — also returned by attendance and event-subscription tools)
or other products' wording keep their product-specific or
product-neutral suggestion instead of a misleading document-permission hint;
member-validation failures such as
“用户不存在/不属于当前组织” are classified as tool errors with a
`--members`-with-`corpId` suggestion instead of a misleading
resource-not-found error; business error output now surfaces the backend
message with `code`/`logId` appended for traceability; and the
`update_permission` / `remove_permission` / `update_member` /
`remove_member` tools — whose servers return a literal `null` on successful
no-payload writes — now render `{}` so downstream JSON consumers do not fail
parsing `null`; other tools keep raw `null` output unchanged.
@@ -0,0 +1,22 @@
---
category: Added
---
- **Permission and member list pagination** (#1085) — `drive/doc permission
list` and `wiki member list` now accept `--next-token` to follow the
server-side cursor (output carries `totalCount`/`hasMore`/`nextToken`) and
map `--limit` to `pageSize` capped at 50 instead of the rejected `maxResults
200` path; `permission add/update/remove` and `wiki member add/update/remove`
additionally accept a `--members` JSON array covering USER/DEPT/CONVERSATION/TAG
grantee types. The optional `--notify` defaults to `false` and is omitted from
the server request unless passed explicitly, so member grants no longer notify
recipients by default. These commands also declare cursor pagination
(`next-token`) in the Agent schema contract, mirroring the internal CLI parity
change. Because a single batch remove can revoke access for up to 30
USER/DEPT/CONVERSATION/TAG members — where departments, chats, and role
groups can indirectly affect many more users — `drive/doc permission
remove` and `wiki member remove` now declare
`confirmation=user_required` and gate the actual tool call behind user
confirmation (`--yes`, an interactive yes, or `--dry-run` preview); their
confirmation-gate failure now also passes through verbatim instead of being
reclassified as a permission-denied or unclassified error.
+3 -1
View File
@@ -25,7 +25,9 @@ category: Added
发布 beta 时,`scripts/release/prepare-changelog.sh` 会按分类和文件名稳定排序,
将未归档 fragments 汇总为唯一的版本章节,并移动到
`.changes/released/<version>/`。因此 release-seal PR 是唯一会修改
`.changes/released/<version>/`。beta 发布后若有新 fragments 合入并直接准备 stable,
stable 封板会把它们追加到明确的 post-beta 小节,并归档到正式版本目录;没有新
fragments 时仍只生成原有 beta 晋级模板。因此 release-seal PR 是唯一会修改
`CHANGELOG.md` 的 PR;它同时归档已消费的 fragments,供审计追溯。
归档只能在同一个 release-seal PR 中以原样移动完成;CI 会拒绝直接修改、
删除或重写已归档文件。
@@ -0,0 +1,5 @@
---
category: Added
---
- **Agoal scorecard search-entities** — `dws agoal scorecard search-entities` searches scorecard metrics and key items by keyword, returning matching entity info (scorecard ID, entity ID, entity type, title, owning team) with optional `--page`/`--page-size` pagination.
+5
View File
@@ -0,0 +1,5 @@
---
category: Added
---
- **AITable datasource shortcuts** — adds 7 shortcuts for datasource sync management (`+datasource-create`, `+datasource-update`, `+datasource-sync`, `+datasource-sync-status`, `+datasource-get-config`, `+datasource-list-sources`, `+datasource-get-fields`) and updates the `dingtalk-aitable` skill with routing rules and a new `aitable-datasource.md` reference guide.
@@ -0,0 +1,13 @@
---
category: Added
---
- **Doc public-link and historical-version reads** — `dws doc read` forwards
the reviewed `password` (internet-public documents with password protection)
and `historyVersion` (read content as of a listed historical version; `0`
denotes the document's initial version) parameters on the markdown, JSONML,
and scope read paths via `--password` / `--version`; `dws doc +fetch` gains
`--password` and `--version` with the same `historyVersion` forwarding, while
`--revision` stays rejected with explicit guidance: revision is the document
edit revision returned by JSONML reads for `+update --expected-revision`
conditional writes, not a historical version number.
@@ -0,0 +1,5 @@
---
category: Added
---
- **OA approval attachment upload** — `dws oa approval attachment upload --file <path>` uploads a local file as an approval attachment in one command: it initializes the upload credential (MCP `oa/init_attachment_upload_info`), HTTP PUTs the file to OSS, then commits it (MCP `oa/commit_attachment_upload_info`). `--file-name` defaults to the file's base name and `--md5` is auto-computed when omitted.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Chat personal emotions** — adds `chat emotion list`, `chat emotion send`, and `chat emotion favorite` for current-user personal favorite emotion listing, sending, and favoriting.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Minutes, DingTalk tasks, and Wiki parameter aliases** — adds reviewed parameter-name normalization, ambiguity guards, and end-to-end payload coverage for the three products.
@@ -0,0 +1,7 @@
---
category: Fixed
---
- **Calendar empty windows** (#1074) — returns a legitimate empty result when the service emits its exact exhausted empty-event sentinel.
- **Task update verification** (#1074) — compares due-time readback as exact milliseconds so committed updates are no longer reported as failures.
- **Comment reaction validation** (#1074) — narrows accepted reaction input to reviewed DingTalk emoji names and rejects Unicode emoji and unsupported names such as `like` and `heart` before the RPC.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **OA, DING, and Report shortcuts** — hardens response, identity, pagination, and confirmation contracts; publishes verified form search, receiver status, and report read workflows while withholding shortcuts that lack trustworthy downstream evidence.
@@ -0,0 +1,11 @@
---
category: Fixed
---
- **OAuth refresh falls back to the organization mirror** — when the server rejects the
current identity's `refresh_token` with the reviewed `invalidParameter.authCode.notFound`
business code, `dws` now retries once with the still-valid token mirrored in the same
organization's slot (same corp, matching or backfilled user identity) before giving up,
and writes the rotated credential back to both the identity and the organization slots so
the fallback stays usable on later refreshes. Transient failures and direct-mode HTTP
rejections without a reviewed business code do not trigger the fallback.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **Stable release sealing** — directly preparing a stable release now renders and archives release fragments merged after its beta baseline, avoiding a forced extra beta solely to consume pending notes.
+5
View File
@@ -0,0 +1,5 @@
---
category: Added
---
- **Sheet revision changesets** — adds read-only commands for querying the current workbook revision and reviewing Agent-readable changes between revisions, with guidance for distinguishing revisions from saved history versions and safely selecting rollback targets.
+14 -4
View File
@@ -534,7 +534,8 @@ jobs:
# where the Schema partition alone owned most of the wall clock. The
# app-<partition> names are pinned to the helper's partition set by
# TestCIAppRacePartitionMatrixMatchesHelper, so a partition can never lose
# its job silently.
# its job silently. The CrossPlatformCoverage-heavy C range is split again
# to retain headroom on runners reclaimed near the five-minute mark.
timeout-minutes: 20
strategy:
fail-fast: false
@@ -542,7 +543,11 @@ jobs:
shard:
- app-schema
- app-a-b
- app-c
- app-c-a-l
- app-c-m-o
- app-c-p-r
- app-c-s-z
- app-c-other
- app-d-r
- app-s-z-example-fuzz
- generators
@@ -678,7 +683,8 @@ jobs:
# partitions run concurrently and each releases its framework registries
# when the process exits; cli/smoke need headroom beyond go test -timeout for
# setup + assembly. The app-<partition> names are pinned to the helper's
# partition set by TestCIAppRacePartitionMatrixMatchesHelper.
# partition set by TestCIAppRacePartitionMatrixMatchesHelper. The
# CrossPlatformCoverage-heavy C range is split again for runner headroom.
timeout-minutes: 20
strategy:
fail-fast: false
@@ -686,7 +692,11 @@ jobs:
shard:
- app-schema
- app-a-b
- app-c
- app-c-a-l
- app-c-m-o
- app-c-p-r
- app-c-s-z
- app-c-other
- app-d-r
- app-s-z-example-fuzz
- generators
+15 -9
View File
@@ -14,9 +14,12 @@ jobs:
uses: actions/github-script@v7
with:
script: |
const webhook = process.env.DINGTALK_WEBHOOK;
if (!webhook) {
console.log('⚠️ DINGTALK_WEBHOOK not set, skipping notification');
const webhooks = [
process.env.DINGTALK_WEBHOOK,
process.env.DINGTALK_WEBHOOK_SECONDARY
].filter(Boolean);
if (webhooks.length === 0) {
console.log('⚠️ No DingTalk webhook configured, skipping notification');
return;
}
@@ -39,12 +42,15 @@ jobs:
}
};
await fetch(webhook, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(message)
});
await Promise.all(webhooks.map(webhook =>
fetch(webhook, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(message)
})
));
console.log('✅ DingTalk notification sent');
console.log(`✅ DingTalk notification sent to ${webhooks.length} webhook(s)`);
env:
DINGTALK_WEBHOOK: ${{ secrets.DINGTALK_WEBHOOK }}
DINGTALK_WEBHOOK_SECONDARY: ${{ secrets.DINGTALK_WEBHOOK_SECONDARY }}
+4 -2
View File
@@ -2698,9 +2698,11 @@ jobs:
;;
compatibility)
test -n "$PREVIOUS_STABLE"
./scripts/policy/check-command-compatibility.sh \
"$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/check-release-compatibility.sh" \
--repo-root "$GITHUB_WORKSPACE" \
--base-ref HEAD \
--stable-ref "$PREVIOUS_STABLE"
--stable-ref "$PREVIOUS_STABLE" \
--candidate-ref HEAD
;;
e2e)
bash scripts/dev/test-multi-profile-e2e.sh
+48
View File
@@ -6,6 +6,54 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th
## [Unreleased]
## [1.0.60-beta.1] - 2026-08-21
### Changed
- **OA, DING, and Report shortcuts** — hardens response, identity, pagination, and confirmation contracts; publishes verified form search, receiver status, and report read workflows while withholding shortcuts that lack trustworthy downstream evidence.
- **Stable release sealing** — directly preparing a stable release now renders and archives release fragments merged after its beta baseline, avoiding a forced extra beta solely to consume pending notes.
### Fixed
- **Calendar empty windows** (#1074) — returns a legitimate empty result when the service emits its exact exhausted empty-event sentinel.
- **Task update verification** (#1074) — compares due-time readback as exact milliseconds so committed updates are no longer reported as failures.
- **Comment reaction validation** (#1074) — narrows accepted reaction input to reviewed DingTalk emoji names and rejects Unicode emoji and unsupported names such as `like` and `heart` before the RPC.
- **OAuth refresh falls back to the organization mirror** — when the server rejects the
current identity's `refresh_token` with the reviewed `invalidParameter.authCode.notFound`
business code, `dws` now retries once with the still-valid token mirrored in the same
organization's slot (same corp, matching or backfilled user identity) before giving up,
and writes the rotated credential back to both the identity and the organization slots so
the fallback stays usable on later refreshes. Transient failures and direct-mode HTTP
rejections without a reviewed business code do not trigger the fallback.
## [1.0.59] - 2026-08-20
This release promotes the sealed `v1.0.59-beta.5` contents to stable.
### Changed
- **Chat personal emotions** — adds commands to list, send, and favorite the current user's personal favorite emotions.
- **Minutes, DingTalk tasks, and Wiki parameter aliases** — adds reviewed parameter-name normalization, ambiguity guards, and end-to-end payload coverage.
- **Shortcut functional workflows** — fixes Drive preview accuracy, AITable write verification and deletion accounting, Wiki feeds, and false-success handling across task, Contact, Minutes, and Wiki operations.
## [1.0.59-beta.5] - 2026-08-20
### Added
- **Chat personal emotions** — adds `chat emotion list`, `chat emotion send`, and `chat emotion favorite` for current-user personal favorite emotion listing, sending, and favoriting.
- **Minutes, DingTalk tasks, and Wiki parameter aliases** — adds reviewed parameter-name normalization, ambiguity guards, and end-to-end payload coverage for the three products.
### Fixed
- **Shortcut functional workflows** (#1050) — fixes truthful Drive push/sync previews, strict AITable write verification and deletion accounting, lossless Wiki feeds, and false-success handling across task, Contact, Minutes, and Wiki operations.
## [1.0.59-beta.4] - 2026-08-20
### Added
+11 -11
View File
@@ -1,33 +1,33 @@
class DingtalkWorkspaceCliBeta < Formula
desc "Automate DingTalk workspace tasks from the terminal (beta channel)"
homepage "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli"
version "1.0.59-beta.4"
version "1.0.60-beta.1"
license "Apache-2.0"
keg_only "it is the beta channel and conflicts with dingtalk-workspace-cli"
on_macos do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59-beta.4/dws-darwin-arm64.tar.gz"
sha256 "f788467e9979c70ef210b411ac915b1506ea77ffa496e26b53cfa99650158721"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.60-beta.1/dws-darwin-arm64.tar.gz"
sha256 "8ef11c79b5c86ec275dd82334232e7582f9e2ba99a66307d7681e42e8f53767b"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59-beta.4/dws-darwin-amd64.tar.gz"
sha256 "a01988709c0dc99dd5874859eb265ba08a6fda412a7ead8303c68e61d2a8b195"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.60-beta.1/dws-darwin-amd64.tar.gz"
sha256 "67612f1dac735984b026c7f8a0dc057beec4cdd029f0a97798bf90aa923eb2d3"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59-beta.4/dws-linux-arm64.tar.gz"
sha256 "8e1a993b2137a082a8cc1d9535dfc2d7b3e4399c76d295840f9dc1f15cca7a0d"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.60-beta.1/dws-linux-arm64.tar.gz"
sha256 "67a8d4f4e0a7d22a9cc53cb91d8c97ecd1152665ce669f68560d86cec5987dd2"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59-beta.4/dws-linux-amd64.tar.gz"
sha256 "26e4cd72cfb96b38ef808863391b81a5c45c3170bca56b5eac457fc601b000c5"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.60-beta.1/dws-linux-amd64.tar.gz"
sha256 "a5fae548b495842779df4291cbcf06d8a2e5ddddf68a41cad1bab1e5c64a1d59"
end
end
resource "skills" do
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59-beta.4/dws-skills.zip"
sha256 "a75107bdc14b5476e097842acc92f798301d8ffb59de9ade01f863d166a89435"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.60-beta.1/dws-skills.zip"
sha256 "9fe12683139a626d32a801dd44158a698f142b61339282e0fc24d4e3a5e97e87"
end
def install
+11 -11
View File
@@ -1,33 +1,33 @@
class DingtalkWorkspaceCli < Formula
desc "Automate DingTalk workspace tasks from the terminal"
homepage "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli"
version "1.0.58"
version "1.0.59"
license "Apache-2.0"
on_macos do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58/dws-darwin-arm64.tar.gz"
sha256 "7d98599f90cae9d42b51ff2863efc87dbfb4a3176ff3c84fc2216110c0157a70"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59/dws-darwin-arm64.tar.gz"
sha256 "61135a2a9286204ce060847e653c63c1e9784a0fa631bb7e0563b90628762a35"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58/dws-darwin-amd64.tar.gz"
sha256 "4c12e35e5bf7e0905812cd42dc94a5345068a2c16e306bb50b13c5c78b5cb95d"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59/dws-darwin-amd64.tar.gz"
sha256 "fd14b0b1a1475891fb243bf6453857a1044ab5a40bcf7dc1c7c795f57e5b03ba"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58/dws-linux-arm64.tar.gz"
sha256 "5ef6bde24bc3db6a11a0f1d0b3343a048956b2cbcf6cd3409a037fb6ba425489"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59/dws-linux-arm64.tar.gz"
sha256 "5bfe9ac7d1798b028f0fad579bbdffec5898e2fb16ee36f5766ab58e208abd50"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58/dws-linux-amd64.tar.gz"
sha256 "3ccadcc6f070a39d2b2ba20429a4fcdc2f21639bf79f34361dc7d16f501bfda6"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59/dws-linux-amd64.tar.gz"
sha256 "be1eb9a1f8fc5048e578b5b0bde212fc90baca0f289236c7c333d824bd869cf3"
end
end
resource "skills" do
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58/dws-skills.zip"
sha256 "2626debc21c3daadfd155b4c167b2219b97e801398fe4441a8b48138960ab264"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59/dws-skills.zip"
sha256 "7ce5c3ab6f6a367407f64971bc5ff96cfcdfade2c1a10d326144b17c7b25a57e"
end
def install
+6 -2
View File
@@ -10,7 +10,7 @@ SCHEMA_META_INDEX_OUTPUT ?= artifacts/schema_meta_index.gob
POLICY_ENV = DWS_POLICY_TMPDIR="$(DWS_POLICY_TMPDIR)" GOTMPDIR="$(POLICY_GOTMPDIR)"
GO_SOURCE_LIST = git ls-files -z --cached --others --exclude-standard -- '*.go'
.PHONY: all help build rebuild test test-plan test-auth-legacy-compat lint format-check fmt policy edition-test interface-integrity authoritative-interface-integrity coverage-gate coverage-gate-platform update-interface-baseline reset-interface-baseline schema-compatibility skill-command-integrity skill-context-budget multi-im-skill-chain-integrity cli-smoke mock-mcp-smoke test-schema-agent-examples generate-schema fetch-mcp-metadata generate-schema-catalog package release release-pre release-stable changelog-pre changelog-stable publish-homebrew-formula setup-hooks
.PHONY: all help build rebuild test test-plan test-auth-legacy-compat shortcut-public-e2e-proof lint format-check fmt policy edition-test interface-integrity authoritative-interface-integrity coverage-gate coverage-gate-platform update-interface-baseline reset-interface-baseline schema-compatibility skill-command-integrity skill-context-budget multi-im-skill-chain-integrity cli-smoke mock-mcp-smoke test-schema-agent-examples generate-schema fetch-mcp-metadata generate-schema-catalog package release release-pre release-stable changelog-pre changelog-stable publish-homebrew-formula setup-hooks
all: setup-hooks fmt lint build test rebuild
@@ -20,6 +20,7 @@ help:
@printf " make test - Run the Go test suite\n"
@printf " make test-plan - Verify CI test and full-suite coverage package plans cover their scopes exactly once\n"
@printf " make test-auth-legacy-compat - Run stable legacy authentication compatibility regressions\n"
@printf " make shortcut-public-e2e-proof - Prove every reviewed Devdoc/HRbrain/PAT public Shortcut through exact and owning raw execution\n"
@printf " make lint - Run formatting checks, go vet, and staticcheck\n"
@printf " make format-check - Check all repository Go source files with gofmt\n"
@printf " make fmt - Format all repository Go source files\n"
@@ -62,6 +63,9 @@ test-auth-legacy-compat:
@mkdir -p "$(POLICY_GOTMPDIR)"
@GO="$(GO)" $(POLICY_ENV) ./scripts/policy/check-auth-legacy-compat.sh
shortcut-public-e2e-proof: build
@GO="$(GO)" DWS_PACKAGE_VERSION="$(DWS_PACKAGE_VERSION)" ./scripts/policy/check-shortcut-public-e2e-proof.sh
lint:
@./scripts/dev/lint.sh
@@ -84,7 +88,7 @@ fmt:
$(GO_SOURCE_LIST) > "$$go_files"; \
xargs -0 sh -c 'if [ "$$#" -gt 0 ]; then exec gofmt -w -- "$$@"; fi' sh < "$$go_files"
policy: test-auth-legacy-compat
policy: test-auth-legacy-compat shortcut-public-e2e-proof
@mkdir -p "$(POLICY_GOTMPDIR)"
@$(POLICY_ENV) ./scripts/policy/check-open-source-assets.sh
@$(POLICY_ENV) ./scripts/policy/check-skill-context-budget.sh
+1 -1
View File
@@ -19,7 +19,7 @@
</p>
> [!IMPORTANT]
> **共创阶段**:本项目涉及钉钉企业数据访问,需企业管理员授权后方可使用。欢迎加入钉钉 DWS 共创群获取支持与最新动态。详见下方 [开始使用](#开始使用)。
> **钉钉 DWS CLI 已全面开放,欢迎使用**:本项目涉及钉钉企业数据访问,需企业管理员授权后方可使用。欢迎加入钉钉 DWS 共创群获取支持与最新动态。详见下方 [开始使用](#开始使用)。
>
> <img src="https://img.alicdn.com/imgextra/i1/O1CN01WJyAsJ1prD2ovQACM_!!6000000005413-2-tps-718-720.png" alt="dws 开源沟通群二维码" width="150">
+63 -5
View File
@@ -13,13 +13,71 @@ if (!fs.existsSync(binaryPath)) {
process.exit(1);
}
const result = childProcess.spawnSync(binaryPath, process.argv.slice(2), {
// Interactive commands must remain in the terminal's foreground session so
// prompts can use /dev/tty. Non-interactive launches use a separate process
// group, allowing a signal sent only to this wrapper to reach the full vendor
// process tree exactly once.
const isolateVendorProcessGroup = process.platform !== "win32" && !process.stdin.isTTY;
const child = childProcess.spawn(binaryPath, process.argv.slice(2), {
stdio: "inherit",
detached: isolateVendorProcessGroup,
});
if (result.error) {
console.error(result.error.message);
process.exit(1);
let spawnFailed = false;
let forwardedSignal = null;
const forwardedSignals = ["SIGINT", "SIGTERM"];
function forwardSignal(signal) {
forwardedSignal = signal;
if (child.exitCode === null && child.signalCode === null) {
if (process.platform === "win32") {
child.kill(signal);
return;
}
if (!isolateVendorProcessGroup) {
// Ctrl-C is generated for the whole foreground process group, including
// the vendor. SIGTERM is not terminal-generated and still needs an
// explicit handoff when a process manager targets only this wrapper.
if (signal === "SIGTERM") {
child.kill(signal);
}
return;
}
try {
// detached makes the vendor PID the leader of its POSIX process group.
// Signal the whole group so any subprocesses inherit the same shutdown.
process.kill(-child.pid, signal);
} catch (error) {
// The group may have completed between the state check and kill.
if (error.code !== "ESRCH") {
throw error;
}
}
}
}
process.exit(result.status === null ? 1 : result.status);
const signalHandlers = new Map(
forwardedSignals.map((signal) => [signal, () => forwardSignal(signal)]),
);
for (const signal of forwardedSignals) {
process.on(signal, signalHandlers.get(signal));
}
child.on("error", (error) => {
spawnFailed = true;
console.error(error.message);
});
child.on("close", (code, signal) => {
for (const forwarded of forwardedSignals) {
process.removeListener(forwarded, signalHandlers.get(forwarded));
}
const exitSignal = forwardedSignal || signal;
if (exitSignal && process.platform !== "win32") {
process.kill(process.pid, exitSignal);
return;
}
process.exitCode = spawnFailed || code === null ? 1 : code;
});
+55 -8
View File
@@ -1,6 +1,11 @@
# CLI Help / Schema 兼容迁移治理
本文定义一种受控迁移:保留旧 flag 的可执行兼容性,但把它从 Help 与 Agent Schema 中隐藏,并将新的规范 flag 设为唯一可见入口。迁移必须保持原 flag 的 requiredness:optional 只能迁到 optional,required 只能迁到 required。它只解决这一种精确变更,不是通用 breaking-change 豁免。
本文定义两种受控 flag 迁移:
1. `flag_rename`:保留旧 flag 的可执行兼容性,但把它从 Help 与 Agent Schema 中隐藏,并将新的规范 flag 设为唯一可见入口;rename 必须保持原 flag 的 requiredness,optional 只能迁到 optional,required 只能迁到 required。
2. `requiredness_change`:同一个公开 flag 从 optional 精确提升为 required;flag 的名称、类型、作用域、可见性、shorthand、`no_opt` 与 alias 关系必须保持不变。
两种原语都只放行清单精确登记的变化,不是通用 breaking-change 豁免,也不得在同一 command/flag 上叠加以绕过 rename 的 requiredness 保持规则。
同一套 base-owned lifecycle 也治理两类跨命令迁移:旧命令保留执行能力但从 Help / Schema 导航隐藏,并迁到新的公开命令路径;或把旧命令中的一个可选 flag 拆成新的专用命令。跨命令迁移只允许清单精确声明的 `command_became_hidden` / `flag_became_hidden` 及其 Schema 投影,不是通用 command-path breaking-change 豁免。
@@ -44,7 +49,9 @@ scripts/policy/interface-migrations/approved-flag-migrations-v1.json
scripts/policy/interface-migrations/approved-command-migrations-v1.json
```
清单使用严格 JSON 解析:版本、字段名大小写、JSON 值类型、命令路径和 flag 名都必须精确;拒绝重复键、未知键、scalar `null` 与尾随 JSON 值,`reason` 不能为空;禁止 `*`、`?`、前缀规则或其他 wildcard。清单中的 `pending` 记录只记录已评审计划,并授权其精确列出的后续产品迁移;候选与 merge-base 仍必须精确匹配 `before`,不能授权同一个提交中的接口变化,也不能作为其他命令或参数的通配豁免。
清单使用严格 JSON 解析:版本、字段名大小写、JSON 值类型、命令路径和 flag 名都必须精确;拒绝重复键、未知键、scalar `null` 与尾随 JSON 值,`reason` 不能为空;禁止 `*`、`?`、前缀规则或其他 wildcard。历史未声明 `kind` 的记录按 `flag_rename` 解释;新增同名 requiredness 迁移必须显式写 `kind: requiredness_change` 和单一 `flag` before/after。清单中的 `pending` 记录只记录已评审计划,并授权其精确列出的后续产品迁移;候选与 merge-base 仍必须精确匹配 `before`,不能授权同一个提交中的接口变化,也不能作为其他命令或参数的通配豁免。
首次引入一个旧 merge-base 不认识的新 `kind` 时,机制 PR 不得同时写入该 kind 的 pending 记录,因为旧的 base-owned 严格解析器会拒绝未知字段。必须先合入 parser、lifecycle、CLI/Schema adapter 与 hostile tests;待这些实现成为新的 merge-base authority 后,再用独立治理审批 PR 新增 pending,最后才由产品 PR 消费。
## 跨命令迁移原语
@@ -68,6 +75,17 @@ optional bool legacy flag,不能隐藏仍由 Cobra hard-required 的参数。
`replacement_constant.value` 与 legacy `no_opt` 都必须是 `true`;negative flag、默认即
`true` 或固定 `false` 的语义不在本轮证明范围,必须另行设计,不能借本清单放行。
如果 `command_move` 的参数 `from` 在更早 stable 中仍使用另一历史名称,Schema adapter
只能把同一 legacy command 上、已经由 base-owned lifecycle 返回且
`state=consumed` 的 flag rename 回执作为前驱边。例如
`group → conversation-id` 与 `conversation-id → open-topic-id` 可以组合,但不能把
candidate 自增的 pending 记录、其他命令的同名参数、参数概念词典或 CLI alias 当作证据。
首次消费 pending command 回执时,merge-base 的 normalized Schema 必须真实发布中间参数,
并逐跳验证参数签名和 constraints;command 回执合入为 consumed 后,中间 Schema 已从 main
消失,此时保留的两份 consumed 回执可继续对 stable 做受限重放,直到 stable 也达到 after
并让回执转为惰性记录或由独立 PR 清理。两种阶段都拒绝残留 predecessor/intermediate、字段漂移、环、分叉、
target 碰撞或 primary path/tool identity 不唯一;positionals 不在该组合授权面内。
`replacement_constant` 不是清单自报即可成立的例外。after 阶段的 Interface Snapshot
必须从 replacement 命令的同一份框架运行时声明中捕获完全一致的 property/value,缺失、
值不符或额外常量都会使 lifecycle 落入 partial。对于 #1054,`dws chat topic create`
@@ -86,22 +104,22 @@ replacement 必须保留 source 已发布的 dry-run 能力:历史 `dry_run`
两种迁移都要求旧 argv 继续可执行。删除旧命令、删除旧 flag、把 legacy 改成 non-runnable、改变未登记的历史参数、改变 interface / safety,或只完成部分 before → after 转换都会 fail closed。命令别名会先规范到 reference 的 canonical path,但清单本身仍只能记录精确 canonical 命令,不能用 alias 或前缀扩大授权。
跨命令清单复用下文同一套 `pending → consumed → cleanup` 生命周期。治理 PR 只能新增 `pending` 且产品 surface 必须仍是 before;后续产品 PR 才能一次性切到 after 并改为 `consumed`。candidate 新增的 pending 记录不能批准自己的改动。
跨命令清单复用下文同一套 `pending → consumed → inert/cleanup` 生命周期。治理 PR 只能新增 `pending` 且产品 surface 必须仍是 before;后续产品 PR 才能一次性切到 after 并改为 `consumed`。candidate 新增的 pending 记录不能批准自己的改动。
当前首批 pending 记录覆盖 `chat topic` 收口:`chat group create --thread` 拆到 `chat topic create`,以及 `chat message list-topic-replies` / `forward-topic` 迁到对应的 `chat topic` 命令。前一条完整登记 `name` / `type` / `users` 的同名承接,以及 `thread` → `convThreadEnabled=true` 的常量承接。产品 PR 消费这些记录时只能把三条 `state` 改为 `consumed`,不得改写其 before、after、Schema mapping、constant 或 reason。
## 两阶段迁移与回执清理
每条迁移以 `(command, legacy flag, canonical flag)` 为唯一精确键,并经历以下生命周期:
rename 以 `(kind, command, legacy flag, canonical flag)` 为唯一精确键;requiredness change 以 `(kind, command, flag)` 为唯一精确键。二者经历同一生命周期:
| 阶段 | PR 可以做什么 | 必须满足的快照状态 |
|---|---|---|
| 1. 治理审批 | 新增 `state: pending` 的精确记录;不得在同一个 PR 修改产品 surface | candidate 和 merge-base 都与记录中的 `before` 完全一致;该记录不改变 stable 的判断 |
| 2. 产品迁移 | merge-base 已拥有 `pending` 后,按记录一次性切到精确 `after`,并把记录改为 `state: consumed` | legacy 仍存在但由 visible 变 hidden,且声明 `alias_of`;canonical 的 requiredness 与 legacy 迁移前完全一致 |
| 2. 产品迁移 | merge-base 已拥有 `pending` 后,按记录一次性切到精确 `after`,并把记录改为 `state: consumed` | rename 的 legacy 仍存在但由 visible 变 hidden,且声明 `alias_of`,canonical requiredness 保持不变;requiredness change 只把同名 flag 从 optional 提升为 required |
| 3. 保留回执 | 产品 PR 合入后,如果 stable 仍是 `before`,继续保留 `consumed` | merge-base 或 stable 仍有任一份尚未达到 `after` |
| 4. 单独清理 | 当 merge-base 和 stable 都已经是 `after`,在后续 PR 删除该记录 | 两份参考快照均精确匹配 `after`;继续保留过期回执会被门禁拒绝 |
| 4. 惰性保留或清理 | 当 merge-base 和 stable 都已经是 `after`,该记录不再提供任何授权;后续 PR 可以原样保留或删除 | 两份参考快照均精确匹配 `after`;保留时仍必须是不可改写的 `consumed`,接口偏离 `after` 继续失败 |
因此,新增 `pending` 和修改产品 surface 不能发生在同一个 PR;candidate 自己新增的记录不能 self-approve。迁移也不能部分执行:legacy、canonical、`alias_of` 或状态只要有一项不匹配,门禁即失败。
因此,新增 `pending` 和修改产品 surface 不能发生在同一个 PR;candidate 自己新增的记录不能 self-approve。迁移也不能部分执行:legacy、canonical、`alias_of` 或状态只要有一项不匹配,门禁即失败。stable 发布只会让已经追平的 `consumed` 回执变成无授权效果的审计记录,不会在没有代码变更时让后续业务 PR 失去合规性;清理仍可作为独立的账本压缩动作,但不再是下一个 PR 的强制前置条件。
下面只是清单结构示例,不代表已审批命令;实际字段必须从 Interface Snapshot 核对:
@@ -146,6 +164,27 @@ replacement 必须保留 source 已发布的 dry-run 能力:历史 `dry_run`
产品迁移 PR 必须保持同一条记录的命令、flag、before/after 和 reason 不变,只把 `pending` 改成 `consumed`。
同名 flag requiredness 迁移的清单结构如下;示例不代表已经审批:
```json
{
"version": 1,
"migrations": [
{
"kind": "requiredness_change",
"command": "dws report entry submit",
"flag": {
"name": "to-user-ids",
"before": {"present": true, "type": "string", "scope": "local"},
"after": {"present": true, "type": "string", "required": true, "scope": "local"}
},
"state": "pending",
"reason": "Reject report submissions that have no visible recipient."
}
]
}
```
## `alias_of` 是框架来源的受评审关系证据
`alias_of` 不是 Schema 同义词、参数概念词典或任意文字声明。它只能由 `FlagSpec.Aliases` 写入,并与内部 origin `corecmd.flag_spec_aliases.v1` 成对出现;每次 Interface Integrity 都会在已提交的 detached candidate 上执行源码门禁,禁止其他生产文件写入或复刻这些 evidence token。Interface Snapshot 会验证:
@@ -166,10 +205,11 @@ replacement 必须保留 source 已发布的 dry-run 能力:历史 `dry_run`
## 豁免边界
一条 base-owned、状态正确且前后快照精确匹配的记录,只会从普通兼容报告中移除以下两类预期 finding:
一条 base-owned、状态正确且前后快照精确匹配的记录,只会从普通兼容报告中移除以下三类预期 finding:
1. legacy flag 的 `flag_became_hidden`(visible → hidden);
2. required legacy 被新增的 required canonical 替代时产生的 `required_flag_added`;如果 canonical 在 before 阶段只是 hidden 占位符,则允许它在转为公开拼写时继承 legacy 的 requiredness。已有的 visible canonical 不允许借 rename 改变 requiredness。
3. `requiredness_change` 中同名 flag 从 optional 提升为 required 时产生的 `flag_became_required`。
以下变化仍按普通兼容规则阻塞,不能被迁移记录掩盖:
@@ -177,6 +217,7 @@ replacement 必须保留 source 已发布的 dry-run 能力:历史 `dry_run`
- flag 类型或迁移记录中的 scope、shorthand、`no_opt` 漂移;
- `alias_of` 缺失、指向变化或 alias chain;
- 命令路径及任何无关的阻塞性接口变化;
- requiredness change 同时发生的 rename、隐藏、类型、scope、shorthand、`no_opt` 或 alias 漂移;
- 不精确、部分完成、超出记录范围的 surface 变化。
## Schema 投影边界
@@ -204,6 +245,12 @@ adapter 先构造经过上述验证的历史 contract 副本,再调用原 Sche
canonical-only `after` 状态时不需要再次投影;adapter 保持 baseline 不变,由原 checker
验证 candidate 是否仍与该 canonical contract 兼容。
`requiredness_change` 的 Schema adapter 只把历史同名 parameter 的 `required` 与
`cli_required` 提升到 candidate 的 `true` 值,并要求 candidate 两者都为 `true`。parameter
不存在、tool/path 不匹配时不制造 Schema surface;type、property、interface type、default、
format、enum、`required_when`、constraints、positionals 与 safety 等全部字段仍交给原 checker,
任何不相干漂移继续阻塞。
## 本地验证
先确保 merge-base 和 stable tag 已在本地,然后运行与 CI 相同的权威门禁:
+6 -3
View File
@@ -3,7 +3,7 @@
Every runtime command the `dws` CLI exposes when loaded with the **pre** environment configuration.
- **Products**: 13
- **Total commands**: 160
- **Total commands**: 163
- **Generated from**: `internal/plugin` command descriptors — the same code path the CLI uses at runtime.
> Auto-generated. Update plugin descriptors in `internal/plugin/`, not this file.
@@ -33,7 +33,7 @@ Every command inherits these flags (documented here once, not repeated per comma
- [`dws aitable` — AI Tables](#dws-aitable) · 41 commands
- [`dws attendance` — Attendance](#dws-attendance) · 4 commands
- [`dws calendar` — Calendar](#dws-calendar) · 14 commands
- [`dws chat` — Group Chat / IM](#dws-chat) · 23 commands
- [`dws chat` — Group Chat / IM](#dws-chat) · 26 commands
- [`dws contact` — Contact Directory](#dws-contact) · 6 commands
- [`dws devdoc` — Open Platform Docs](#dws-devdoc) · 2 commands
- [`dws ding` — DING Messages](#dws-ding) · 2 commands
@@ -134,12 +134,15 @@ _Calendar events, participants, meeting rooms, and busy-status queries._
_Group chats, conversations, messages, and robot/webhook integrations._
**23 commands**
**26 commands**
| Command | Description | When to use |
|---|---|---|
| `dws chat bot search` | Search robots (bots) created by the current user by keyword. | When the agent needs to resolve one of its own bots by name to a robot code before sending bot messages. |
| `dws chat conversation-info` | Retrieve basic metadata for a conversation (single chat or group chat) by conversation ID. | When the agent needs context about a conversation (name, type, member count) before operating on it. |
| `dws chat emotion favorite` | Add a media ID to the current user's personal favorite emotions. | When the agent needs to save an available mediaId as a reusable personal emotion, optionally preserving source message context. |
| `dws chat emotion list` | List the current user's personal favorite emotions. | When the agent needs to inspect available personal emotions or resolve an emotionId/mediaId before sending. |
| `dws chat emotion send` | Send a personal favorite emotion to a group or direct chat as the authenticated user. | When the agent needs to send a known personal emotion mediaId to exactly one group, userId, or openDingTalkId target. |
| `dws chat group create` | Create a new internal group chat with a set of initial members. | When the agent needs to spin up a dedicated group for a new project, incident, or discussion thread. |
| `dws chat group members` | List members of a group chat; can also be used against the current user to enumerate their groups' members. | When the agent needs the roster of a group before mentioning, removing, or auditing members. |
| `dws chat group members add` | Add one or more users to an existing group chat. | When the agent expands a group to include additional participants. |
+454
View File
@@ -0,0 +1,454 @@
# AI 表格数据源指令使用指南
## 概述
dws 新增了 7 个 AI 表格数据源同步管理指令,用于将外部数据源(一期支持审批数据)接入 AI 表格,实现数据的自动同步。
所有指令均通过 `dws aitable +datasource-*` 前缀调用,操作对象是 AI 表格中的"数据源表"——一种由数据源同步创建的特殊数据表。
## 指令速览
| 指令 | 用途 | 读写 | 风险 |
|------|------|------|------|
| `+datasource-list-sources` | 列出数据源类型可用的来源信息(OA 返回 result/processCode、sourceType、sourceUrl) | 读 | low |
| `+datasource-get-fields` | 获取数据源来源的可同步字段结构 | 读 | low |
| `+datasource-create` | 创建数据源表并触发首次同步 | 写 | medium |
| `+datasource-update` | 更新已有数据源表的同步配置 | 写 | medium |
| `+datasource-sync` | 手动触发一次同步 | 写 | medium |
| `+datasource-sync-status` | 查询同步任务状态 | 读 | low |
| `+datasource-get-config` | 查看数据源表配置 | 读 | low |
## 前置条件
1. **登录认证**:执行 `dws auth login` 确保已登录
2. **获取 Base ID**:通过 `dws aitable +base-list` 或 `dws aitable +base-search --query "关键词"` 获取目标 AI 表格的 Base ID
---
## 1. 列出数据源可用来源
```
dws aitable +datasource-list-sources [flags]
```
列出指定数据源类型可用的来源信息。OA 审批类型返回当前 Base 可用的审批数据源条目(`sources` 数组,当前通常为单条),用于构造 `+datasource-create` / `+datasource-update` / `+datasource-get-fields` 的 `--source-config`。OA 场景下每条 source 的 `result` 字段是 JSON 字符串,需解析后得到 `approvals` 数组,再从中提取目标模板的 `processCode`、`name`、`iconUrl`、`url`。
### 参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `--base-id` | string | 是 | 目标 Base ID |
| `--datasource-type` | string | 是 | 数据源类型,目前支持审批(OA) |
### 示例
```bash
# 列出审批数据源来源,获取 result(JSON,解析后得到 approvals[].processCode)
dws aitable +datasource-list-sources \
--base-id BASE123 \
--datasource-type OA
```
### 返回值
返回 `sources` 数组,每个条目包含:
| 字段 | 说明 |
|------|------|
| `result` | OA 审批场景为 JSON 字符串,解析后得到 `approvals` 数组;每个 approval 含 `processCode`、`name`、`iconUrl`、`url` |
| `sourceType` | 数据源类型编号(OA 对应内部枚举值 2) |
| `sourceUrl` | 数据源访问链接,可选 |
`result` 本身不是 `processCode`,需要解析出 `approvals` 数组,再取目标模板的 `processCode`、`name`、`iconUrl`、`url` 原样填入 `--source-config`。
---
## 2. 获取数据源可同步字段
```
dws aitable +datasource-get-fields [flags]
```
获取指定数据源来源(如某个审批模板)的可同步字段列表,包括字段 ID、字段名称、字段类型和是否主键等信息。用于创建数据源前选择需要同步的字段。
### 参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `--base-id` | string | 是 | 目标 Base ID |
| `--datasource-type` | string | 是 | 数据源类型,目前支持审批(OA) |
| `--source-config` | string | 是 | 源配置 JSON 字符串,结构同 `+datasource-create` 的 `--source-config` |
### 示例
```bash
# 获取某审批模板的可同步字段
dws aitable +datasource-get-fields \
--base-id BASE123 \
--datasource-type OA \
--source-config '{"processCode":"PROC-XXXX","name":"采购申请","dataType":"recent_time","recentDays":"30d","iconUrl":"https://example.com/icon.png","url":"https://example.com/oa"}'
```
### 返回值
返回可同步字段列表,每个字段包含字段 ID、名称、类型和是否主键。字段 ID 可用于 `+datasource-create` / `+datasource-update` 的 `--field-ids` 参数。
---
## 3. 创建数据源表
```
dws aitable +datasource-create [flags]
```
为指定 AI 表格创建数据源同步配置,自动创建一张数据源表并触发首次全量同步。
### 参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `--base-id` | string | 是 | 目标 Base ID(通过 `+base-list` / `+base-search` 获取) |
| `--datasource-type` | string | 是 | 数据源类型,目前支持审批(OA) |
| `--source-config` | string | 是 | 源配置 JSON 字符串(格式见下方) |
| `--auto` | bool | 否 | 是否开启自动同步,默认 false;无论是否传入,CLI 都会把该字段下发给下游 |
| `--auto-sync-setting` | string | 否 | 自动同步频率配置 JSON 字符串,仅在 `--auto=true` 时生效,格式见下方 |
| `--field-ids` | stringSlice | 否 | 需要同步的字段 ID 列表,不传时同步全部字段 |
### source-config 格式(审批类)
审批数据源的 `--source-config` 是一个 JSON 对象字符串,包含以下字段:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `processCode` | string | 是 | 审批模板编码,对应 `+datasource-list-sources` 返回的 `result` |
| `name` | string | 是 | 数据源展示名称,须从 `+datasource-list-sources` 结果原样透传 |
| `iconUrl` | string | 是 | OA 审批图标 URL,须从 `+datasource-list-sources` 结果原样透传 |
| `url` | string | 是 | OA 审批跳转链接,须从 `+datasource-list-sources` 结果原样透传 |
| `dataType` | string | 是 | 数据时间范围类型:`time_range` / `start_time` / `recent_time` |
| `recentDays` | string | 当 dataType=recent_time 时必填 | 近 N 天:`7d` / `30d` / `1y` |
| `startDate` | string | 当 dataType=time_range 或 start_time 时必填 | 起始日期,格式 `yyyy-MM-dd` |
| `endDate` | string | 当 dataType=time_range 时必填 | 结束日期,格式 `yyyy-MM-dd` |
| `keepRemovedFields` | bool | 否 | 是否保留已删除字段,默认 false |
> 注:`splitParentTableField`、`enableDataSyncOaDetailList` 等字段为下游内部字段,无需传入,下游自动处理。
按 `dataType` 选择对应的时间参数组合:
| dataType | 需要的时间字段 | 说明 |
|----------|----------------|------|
| `recent_time` | `recentDays` | 同步近 N 天数据(7d/30d/1y) |
| `start_time` | `startDate` | 同步从某日期至今的数据 |
| `time_range` | `startDate` + `endDate` | 同步指定日期范围内的数据 |
### auto-sync-setting 格式
`--auto-sync-setting` 仅在 `--auto=true` 时生效,用于指定自动同步频率。不传时使用下游默认策略。
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `syncType` | string | 是 | `hourly`(按小时间隔)/ `scheduled`(定时触发) |
| `hourlyInterval` | int | hourly 时必填 | 正整数,小时间隔 |
| `scheduleType` | string | scheduled 时必填 | `daily` / `weekly` / `monthly` |
| `timeValue` | string | scheduled 时必填 | 触发时间,格式 `HH:mm` |
| `selectedMonthDays` | int[] | monthly 时必填 | 每月几号触发,1-31 |
| `selectedWeekdays` | int[] | weekly 时必填 | 每周哪几天触发,1=周一…7=周日 |
| `skipNonWorkingDay` | bool | 否 | 是否跳过非工作日,默认 false |
示例:`{"syncType":"scheduled","scheduleType":"daily","timeValue":"09:00"}`
### 示例
```bash
# 基本创建——同步近 30 天审批数据
dws aitable +datasource-create \
--base-id BASE123 \
--datasource-type OA \
--source-config '{"processCode":"PROC-XXXX","name":"采购申请","dataType":"recent_time","recentDays":"30d","iconUrl":"https://example.com/icon.png","url":"https://example.com/oa"}'
# 指定日期范围创建并开启自动同步
dws aitable +datasource-create \
--base-id BASE123 \
--datasource-type OA \
--source-config '{"processCode":"PROC-XXXX","name":"采购申请","dataType":"time_range","startDate":"2025-01-01","endDate":"2025-12-31","iconUrl":"https://example.com/icon.png","url":"https://example.com/oa"}' \
--auto
# 指定同步字段(仅同步部分字段,field-ids 可通过 +datasource-get-fields 获取)
dws aitable +datasource-create \
--base-id BASE123 \
--datasource-type OA \
--source-config '{"processCode":"PROC-XXXX","name":"采购申请","dataType":"recent_time","recentDays":"30d","iconUrl":"https://example.com/icon.png","url":"https://example.com/oa"}' \
--field-ids fldAAA,fldBBB,fldCCC
```
### 返回值
创建成功后返回新建数据源表 ID 和同步任务 ID,后续操作需要用到这两个 ID。
---
## 4. 更新数据源配置
```
dws aitable +datasource-update [flags]
```
更新已有数据源表的同步配置,支持更新源配置、自动同步开关和同步字段选择。更新后会自动触发一次同步。
### 参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `--base-id` | string | 是 | 目标 Base ID |
| `--table-id` | string | 是 | 已存在的数据源表 ID(由 `+datasource-create` 返回) |
| `--source-config` | string | 否 | 新的源配置 JSON 字符串,不传时保持原有配置。结构同 `+datasource-create` |
| `--auto` | bool | 否 | 是否开启自动同步;仅显式设置时下发给下游,省略时保持原有自动同步开关不变 |
| `--auto-sync-setting` | string | 否 | 自动同步频率配置 JSON 字符串,仅在显式设置 `--auto=true` 时生效;省略时保持原频率配置 |
| `--field-ids` | stringSlice | 否 | 需要同步的字段 ID 列表,不传时保持现有字段配置 |
### 示例
```bash
# 更换审批模板并调整时间范围
dws aitable +datasource-update \
--base-id BASE123 \
--table-id TBL456 \
--source-config '{"processCode":"PROC-YYYY","name":"出差申请","dataType":"recent_time","recentDays":"30d","iconUrl":"https://example.com/icon.png","url":"https://example.com/oa"}'
# 开启自动同步
dws aitable +datasource-update \
--base-id BASE123 \
--table-id TBL456 \
--auto
# 更新同步字段范围
dws aitable +datasource-update \
--base-id BASE123 \
--table-id TBL456 \
--field-ids fldAAA,fldDDD
```
> 注意:`--table-id` 指向的是数据源表(由 `+datasource-create` 创建),不是普通数据表。
---
## 5. 触发手动同步
```
dws aitable +datasource-sync [flags]
```
对已有数据源表触发一次手动同步。单次最多 5 张表,每张表独立提交,部分失败不影响其他表。
### 参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `--base-id` | string | 是 | 目标 Base ID |
| `--table-ids` | stringSlice | 是 | 待触发同步的数据源表 ID 列表(1-5 个) |
### 示例
```bash
# 同步单张表
dws aitable +datasource-sync \
--base-id BASE123 \
--table-ids TBL1
# 批量同步多张表(逗号分隔,最多 5 个)
dws aitable +datasource-sync \
--base-id BASE123 \
--table-ids TBL1,TBL2,TBL3
```
### 返回值
返回每个表的同步任务 ID,可通过 `+datasource-sync-status` 查询最终结果。
---
## 6. 查询同步状态
```
dws aitable +datasource-sync-status [flags]
```
按任务 ID 查询数据源表的同步任务状态。与 `+datasource-sync` / `+datasource-create` / `+datasource-update` 配对使用——这些指令触发同步后返回任务 ID,本指令通过任务 ID 查询最终结果。
### 参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `--base-id` | string | 是 | 目标 Base ID |
| `--table-id` | string | 是 | 数据源表 ID |
| `--task-ids` | stringSlice | 是 | 待查询的同步任务 ID 列表(1-5 个) |
### 示例
```bash
# 按任务 ID 查询(批量,最多 5 个)
dws aitable +datasource-sync-status \
--base-id BASE123 \
--table-id TBL456 \
--task-ids TASK1,TASK2
```
---
## 7. 获取数据源配置
```
dws aitable +datasource-get-config [flags]
```
获取指定数据源表的同步配置信息,包括源配置、同步模式、自动同步开关和同步状态。
### 参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `--base-id` | string | 是 | 目标 Base ID |
| `--table-id` | string | 是 | 数据源表 ID |
### 示例
```bash
dws aitable +datasource-get-config \
--base-id BASE123 \
--table-id TBL456
```
---
## 典型工作流
### 场景一:从零接入审批数据
```bash
# 0. 获取 Base ID
dws aitable +base-search --query "我的项目表"
# 1. 列出可用审批数据源来源,解析 result JSON 获取 approvals[].processCode
dws aitable +datasource-list-sources \
--base-id BASE123 \
--datasource-type OA
# → 返回 sources[0].result 为 JSON 字符串,解析后取 approvals[0].processCode=PROC-XXXX
# 2. 查看可同步字段(可选,用于指定 field-ids)
dws aitable +datasource-get-fields \
--base-id BASE123 \
--datasource-type OA \
--source-config '{"processCode":"PROC-XXXX","name":"采购申请","dataType":"recent_time","recentDays":"30d","iconUrl":"https://example.com/icon.png","url":"https://example.com/oa"}'
# 3. 创建数据源表(创建后自动触发首次同步)
dws aitable +datasource-create \
--base-id BASE123 \
--datasource-type OA \
--source-config '{"processCode":"PROC-XXXX","name":"采购申请","dataType":"recent_time","recentDays":"30d","iconUrl":"https://example.com/icon.png","url":"https://example.com/oa"}'
# → 返回 tableId=TBL456, taskId=TASK001
# 4. 查询首次同步是否完成
dws aitable +datasource-sync-status \
--base-id BASE123 \
--table-id TBL456 \
--task-ids TASK001
# 5. 确认配置
dws aitable +datasource-get-config \
--base-id BASE123 \
--table-id TBL456
```
### 场景二:更换审批模板后重新同步
```bash
# 1. 更新源配置(更新后自动触发一次同步)
dws aitable +datasource-update \
--base-id BASE123 \
--table-id TBL456 \
--source-config '{"processCode":"PROC-NEW","name":"新审批模板","dataType":"recent_time","recentDays":"30d","iconUrl":"https://example.com/icon.png","url":"https://example.com/oa"}'
# 2. 查询同步状态(更新后会返回新的 taskId)
dws aitable +datasource-sync-status \
--base-id BASE123 \
--table-id TBL456 \
--task-ids TASK002
```
### 场景三:手动触发日常同步
```bash
# 仅触发同步,不修改配置
dws aitable +datasource-sync \
--base-id BASE123 \
--table-ids TBL456
# 查询结果(sync 会返回 taskId)
dws aitable +datasource-sync-status \
--base-id BASE123 \
--table-id TBL456 \
--task-ids TASK001
```
### 场景四:开启自动同步后确认
```bash
# 1. 更新配置,开启自动同步
dws aitable +datasource-update \
--base-id BASE123 \
--table-id TBL456 \
--auto
# 2. 确认配置已更新
dws aitable +datasource-get-config \
--base-id BASE123 \
--table-id TBL456
# → 返回中应显示 auto=true
```
---
## 通用选项
以下全局选项可在所有指令中使用:
| 选项 | 说明 |
|------|------|
| `-f, --format` | 输出格式:json(默认)/ table / raw / pretty / ndjson / csv |
| `--jq` | jq 表达式过滤输出(如 `.tableId` 或 `.status`) |
| `--fields` | 筛选输出字段(逗号分隔) |
| `--dry-run` | 预览操作内容,不实际执行 |
| `--profile` | 指定组织或账号 |
| `--timeout` | HTTP 请求超时时间(秒,默认 30) |
| `--debug` | 显示调试日志 |
| `-v, --verbose` | 显示详细日志 |
### 输出过滤示例
```bash
# 只取 tableId
dws aitable +datasource-create ... --jq '.tableId'
# 只取同步状态
dws aitable +datasource-sync-status ... --jq '.status'
# table 格式查看
dws aitable +datasource-get-config ... -f table
```
---
## 注意事项
1. **推荐流程**:先 `+datasource-list-sources` 解析 `result` JSON 获取 `approvals[].processCode`,再 `+datasource-get-fields` 查看可同步字段,最后 `+datasource-create` 创建数据源表。
2. **数据源表 vs 普通数据表**:`+datasource-create` 创建的是"数据源表",它由数据源同步驱动数据写入。`+datasource-update` 和 `+datasource-sync` 仅适用于数据源表,不可对普通数据表使用。
3. **datasource-type 透传**:CLI 层不对 `--datasource-type` 做枚举校验,目前一期仅支持 `OA`(审批)。后续支持其他类型时由服务端控制,CLI 无需修改。
4. **source-config 格式**:`--source-config` 必须是合法 JSON 字符串。审批数据源需要原样透传 `processCode`(从 `+datasource-list-sources` 返回的 `result` JSON 中解析 `approvals[]` 提取)、`name`、`iconUrl`、`url`,设置 `dataType`(时间范围类型),并按 `dataType` 提供对应的时间参数(`recentDays` / `startDate` / `endDate`)。
5. **同步限制**:`+datasource-sync` 单次最多 5 张表;`+datasource-sync-status` 单次最多查询 5 个任务 ID。
6. **创建即同步**:`+datasource-create` 和 `+datasource-update` 在操作完成后会自动触发一次同步,无需额外调用 `+datasource-sync`。
7. **自动同步**:`--auto` 开启后,数据源表会按 `--auto-sync-setting` 指定的频率自动定期同步;未指定频率时使用服务端默认策略。关闭 `--auto` 后仅能通过 `+datasource-sync` 手动触发。
@@ -0,0 +1,26 @@
# Agoal CLI 参数幻觉复核(2026-08-21)
## 结论
Agoal 是本轮全产品复核中新发现的真实漏项。基于最新线上 `main`
`11934eed057267d97e7442ddd420c711ee1802dc`,已形成独立完整候选:62 个 concept、362 个
command override、691 条 fixture。候选仅保存在本目录,未修改正式表。
## 必要兜底
- `obj-template list` 与 `report submit-detail` 复用搜索词、页码、页大小概念;它们是数字分页,明确阻断 cursor/offset。
- `strategy detail/update` 仅在命令内把 `strategy-id` 归一到 API 的 `profile-id`;该 ID 不是账号 profile、userId 或 corpId。
- `contract`、`scorecard`、`user objectives` 只加入角色完整的命令局部别名;`id`、`date`、`payload` 等继续 fail-closed。
- 覆盖式更新的 JSON 字段只允许精确字段名加 `-json`,不把通用 `data/payload/json` 猜成业务字段。
## 未自动解决
`scope-id` 的值域由 `scope-type=DEPT|PERSONAL` 决定,单靠参数名无法把 department-id 或
user-id 安全转换;更新命令仍必须先查详情再覆盖提交。以上属于值语义与工作流,不由 argv alias 代替。
## 验证
独立候选完成 fresh generate、嵌入式 PreParse fixture、alias/canonical、guard 和非目标保持验证;
联合候选新增 13 个有效命令规则,产生 14 个 alias、4 个 block、25 个 ambiguous 行为差异。
分析依据:最新 Cobra/Help、正式 Schema、`dingtalk-misc/references/agoal.md`、真实生成器与 Runtime PreParse。
File diff suppressed because one or more lines are too long
@@ -0,0 +1,180 @@
# AI Search 产品 CLI 参数幻觉分析
## 结论摘要
本分析以线上 `origin/main` 冻结提交
`aa4ae9a90323aa97e5cebdb5045b129f0f14e0df` 为唯一基线,使用该提交重新构建的
`dws`、运行时 Schema、Cobra Help、AI Search 实现、dingtalk-aisearch Skill 及冻结正式
`internal/cli/param_concepts.json`。未使用固定 Catalog、历史 badcase、用户 Shortcut 或
已安装插件,也没有修改当前工作区正式别名表。
AI Search 有 3 个 Agent 可见 Schema 叶:`person`、`enterprise`、`behavior`;另有一个
可执行兼容父命令 `dws aisearch --query ...`,等价于 person。`person` 还注册 11 个命令
路径 alias。冻结正式别名表对 AI Search 完全没有 concept、override 或 fixture。主要风险
是槽位幻觉:把人员目标与 dimension 合成一个 flag,把企业内容的 queries/types/time-range
塞进一句自然语言,把行为 action/direction/chat-scope 混成 sender/receiver/group ID,或
把完整手机号、已知 userId 继续送进语义搜人。
候选已通过真实生成器、PreParse、4 组 alias/canonical dry-run 逐字节比较、15 组代表
block/ambiguous、非目标结构恒等、`internal/cli`、`internal/pipeline`、generated drift 和
Schema Catalog 政策。完整 `internal/app` 运行 291.439 秒后仅
`TestCrossPlatformCoverageReviewedParamAliasesHaveCompleteTemplatesAndRepresentativeFinalPayloads`
失败:4 个作用域缺 complete-command E2E 模板,13 条 active alias fixture 未进入最终
payload 等价验证。正式状态为“规则与链路已验证,补 4 个模板后方可落地”。
## 参数问题
### 1. 可执行父命令、Schema 叶和命令路径 alias 容易混为一层
规范路径是 `aisearch person`,但冻结 Cobra 还接受裸 `aisearch --query`,以及
`search/search-person/search-user/user/user-search/query/people/ask/find/lookup/contact` 等
person 路径 alias;enterprise 也有 knowledge/content 等路径 alias。Schema 只发布三个
规范叶,不发布可执行父命令。
命令路径 alias 不属于参数 concept。候选为可执行父命令单独建立精确作用域,只允许人员
query/dimension 的参数别名,并 block enterprise/behavior 槽位;不把 `contact` 路径 alias
解释成通讯录产品参数,也不改任何现有命令身份。
### 2. person 的 query 与 dimension 必须成对表达不同角色
`--query` 是完整保真的人员搜索目标,`--dimension` 是
all/name/department/position/duty/supervisor/subordinate/phone/jobNumber 的逗号分隔枚举。
`--job-number W123`、`--department 产品部` 或 `--phone 138...` 都不是同义 flag:它们还
隐含必须设置 dimension,中央表不能一次生成两个参数。
候选新增 person dimension concept,允许 `dimensions/search-dimension/search-by`;
`search-term/person-query/person-name → query` 只传原值。role-shaped
`job-number/department-id/mobile` block,泛化 `target/phone/department/duty` ambiguous。
维度枚举值本身不做大小写、连字符或中文转换。
### 3. 完整手机号与已知 userId 属于 Contact,不是参数别名
手机号语义线索可用 `person --query ... --dimension phone`;完整手机号精确反查必须用
`contact user search-mobile --mobile`。拿到 userId 后查邮箱/部门/职位必须用
`contact user get --ids`。多候选不能自动取第一个。
候选在 person/root 上 block `mobile/phone-number/user-id/open-dingtalk-id/ids`,避免把
跨产品 SOP 误装成 `query` alias;`phone/id/target` 需消歧。别名层不检查手机号是否完整,
不查人员、不唯一化候选,也不编造返回字段。
### 4. enterprise 要求 queries、types、time-range 已完成槽位拆分
`--queries` 只放主题词;`--types` 是
all/document/im/calendar/todo/minute/report/image/link/notable/baike/mail 的 CSV;
`--time-range` 只接用户明确给出的时间词。诸如“最近 OKR 相关邮件”必须拆成
`queries=OKR`、`types=mail`、`time-range=最近`,不能把原句放进 query。
候选新增 content queries/types/time-range 三个 concept,允许 topics/content-query、
content-types/resource-types、time-window/date-range 等角色完整的名称。`question`、
`natural-language`、泛化 `type/source/range/date/time` ambiguous;behavior/direction、人员 ID
和资源 ID block。候选不从字符串中剥离时间/类型词,也不自动补 types。
### 5. behavior 的 action、direction 与 chat-scope 不能由零散角色拼装
behavior 在 enterprise 三槽之外还有:
- `--behavior-type=all|send|create|share|edit|receive`;
- `--direction` 完整字符串,如 `我->汐峰`;
- `--chat-scope` 仅在 IM 且用户明确群名时使用。
候选新增 behavior type、direction、chat scope concept;`action-type → behavior-type`、
`interaction-flow → direction`、`group-name → chat-scope`。`from/to/sender/receiver` block,
因为别名表不能组合 direction;`chat-id/group-id` block,因为真实参数要自然群名而非 ID;
泛化 action/actor/target/chat/group ambiguous。它也不会因 chat-scope 自动设置 types=im。
### 6. 单值 query、复数 queries 和 CSV 枚举不能自动互换或改值
person `query` 是一个完整搜索目标;enterprise/behavior `queries` 是零到多个主题 CSV。
types、dimension 也是 CSV,但值域不同。Runtime 仅对 search type 的 `doc` 做
`document` 兼容,其他中文词、复数、行为同义词或 `job-number → jobNumber` 不会被候选
转换。
候选因此不复用正式 `search_query`,而建立独立 `aisearch_content_queries`;person 上
`queries` block,enterprise/behavior 上 `dimension` block。所有 concept 都排除无角色
type/category/scope,值保持原样。
### 7. 原生 hidden flags 与 Help/Schema 的兼容面需单独治理
person 原生接受 hidden `keyword/name/q/text/type`;enterprise/behavior 原生接受 hidden
`query/keyword/search-types/searchTypes/timeRange`,behavior 还接受 chatScope/behaviorType。
这些是真实 Cobra flag,生成器禁止中央表 rewrite/block。`type` 在 person 只允许
person/user/people,与 dimension 完全不同。
候选保持这些原生兼容路径,不重复实现。正式后续应评审哪些 hidden flag 应发布到
Help/Schema、哪些只保留兼容期,以及 `contact` 命令路径 alias 是否会放大跨产品误路由;
这些都不是 param_concepts 的所有权。
## 当前别名表可以实施的方案
1. 新增 person dimension、内容 queries/types/time-range、behavior type/chat scope/direction
七个严格命令范围 concept。
2. 为可执行父命令和三个规范叶建立 4 个 `scope_strict` override。
3. 对同角色名称做原值映射,对完整手机号、已知 ID、资源读写、跨槽输入做 block,对
`target/type/source/range/action/chat` 等做 ambiguous。
4. 保持命令路径 alias 和所有 hidden native flag 原生;不重复覆盖。
5. 补齐 4 个 complete-command payload 模板后再评审正式替换。
## 当前能力支持不了的事项
- 从完整自然语言自动抽取 query/queries、dimension、types、time-range、behavior-type;
- 一次把 `--job-number/--department/--phone` 改写成 query 加 dimension 两个 flag;
- 判断手机号是否完整并自动切换到 contact;
- 把 userId/openDingTalkId 补成联系人详情,或在多候选中自动选择;
- 把单值 query 与 CSV queries 自动拆分/合并;
- 把中文类型词、行为同义词或 `job-number` 改成枚举值;
- 从 sender/receiver/from/to 拼装 direction;
- 从 chat/group ID 查询群名,或因 chat-scope 自动补 types=im;
- 用别名表新增/删除命令路径 alias 或修改 Help/Schema hidden flag;
- 在没有 complete-command 模板时直接替换正式表。
## 第一轮改造建议
第一轮建议落地 7 个 typed concept 和 4 个作用域的低风险别名/保护。落地 PR 必须为
`aisearch`、`aisearch person`、`aisearch enterprise`、`aisearch behavior` 补
complete-command E2E 模板,覆盖 13 条 active fixture。另行评审父命令与 person 的路径
alias、hidden type/query 兼容面及 Skill 中“完整目标保真”和“剥离维度词”的表述一致性。
## 候选 `param_concepts.json` 改动与审核
- 新增 7 个 AI Search 专用 concept;未扩大任何既有 concept;
- 新增 4 个 command override;
- 新增 30 个 fixture,其中 13 个 active alias、17 个 block/ambiguous;
- `go generate ./internal/cli` 从 569 个命令作用域变为 573 个;
- 非目标 concept、override、fixture 结构恒等;
- 生成 Go diff 只新增 4 个 AI Search entry,fallback 无变化;
- 4 组代表 alias/canonical stdout/stderr 逐字节相同;
- 15 组直接保护检查稳定返回 `blocked_flag` 或 `ambiguous_flag`;
- 审核中发现 `group-name` 已归正式 `group_name` concept,已移出新 concept,改为 behavior
命令级 alias,避免跨产品语义合并。
候选位置:`docs/parameter-hallucination/aisearch/param_concepts.json`。
## 验证结果与正式替换前置条件
| 验证项 | 结果 | 说明 |
|---|---|---|
| JSON 解析与生成器 | 通过 | 573 个命令作用域 |
| PreParse 与 alias/canonical | 通过 | root-person、person、enterprise、behavior 四组逐字节一致 |
| block/ambiguous | 通过 | 15 组代表性错误均在派发前停止;候选共 17 条保护 fixture |
| 原生参数 | 通过 | hidden flags 与命令路径 alias 保持原生 |
| 非目标回归 | 通过 | JSON 结构恒等;生成 diff 仅 4 个 entry;fallback 无变化 |
| `internal/cli`、`internal/pipeline` | 通过 | CLI 80.576 秒 |
| generated drift | 通过 | alias 与 Schema 双次装配确定 |
| Schema Catalog 政策 | 通过 | 28 产品、1166 工具;Runtime confirmation truth 通过 |
| 完整 `internal/app` | 未通过 | 291.439 秒;仅 complete-command 覆盖测试失败 |
| complete-command payload 门禁 | 未通过 | 200/204 个活跃命令已有模板;AI Search 缺 4 个命令、13 条 active fixture;392 active cases |
正式替换前必须补齐 4 个模板并重跑完整 `internal/app` 和政策门禁;未完成前,本候选只
作为完整待审核草稿。
## 分析依据
- 冻结提交:`aa4ae9a90323aa97e5cebdb5045b129f0f14e0df`,提交时间
2026-08-20 10:53:27 +08:00。
- 正式表 SHA-256:
`e41e7908c26dfdcc23636d27661d705416c001d67a6d0d5d658b1ca4bcc815c1`。
- 候选 SHA-256:
`1742e69d628c3f99945201acc9cd1a46fb7b93a1e080a9f19294a87a97eba514`。
- 命令实现:`internal/helpers/aisearch.go`。
- Skill:dingtalk-aisearch 根 Skill、aisearch reference、intent guide、lite recipes。
- Schema 来源:同一冻结二进制运行时声明组装;未使用历史或固定 Catalog。
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,269 @@
# DWS AI 表格 CLI 参数幻觉与参数契约分析
初次分析:2026-08-10
本次复核:2026-08-17
产品面基线:`origin/main@104eb715c4d5b110cb3442078356a2ec091f57bd`
最新兼容复核:`origin/main@a5b9e5a13f71`
分析对象:DWS `aitable` 的正式原生命令、仓库内置快捷命令、运行时 Schema、Cobra Help、产品 Skill、最新正式中央参数别名表。
## 1. 结论摘要
原报告需要更新。最新 `main` 通过 `819355b3 feat: add aitable workflow run and history commands` 新增了两个原生命令:
- `dws aitable workflow run`:立即执行工作流,是需要用户确认的写命令;
- `dws aitable workflow history`:按状态、Unix 毫秒时间范围和零基页码查询工作流执行历史。
除这两个新增命令外,机器对账确认旧基线的 209 个 AITable 工具在 `canonical_path`、`cli_path` 和完整参数集合上均未变化。因此,原分析的 11 类根因仍成立,但数量和候选别名表必须以最新正式表重建,不能继续使用 2026-08-10 的旧草稿直接覆盖。
更新后的产品面为:
| 指标 | 旧基线 | 最新 main | 变化 |
|---|---:|---:|---:|
| Agent 可见工具 | 209 | 211 | +2 |
| 原生 primary tools | 117 | 119 | +2 |
| 内置 `+` shortcut | 92 | 92 | 0 |
| 参数出现次数 | 735 | 746 | +11 |
| 不同公开参数名 | 114 | 118 | +4 |
新增的四个公开参数名是 `after-time`、`before-time`、`page` 和 `status`。两个新命令同时复用了 `base-id`、`workflow-id`、`table-id`、`record-ids`、`size` 等既有参数,因此带来以下新增风险:
1. `workflow run --record-id` 在没有保护时会被通用编辑距离纠错静默改成 `--record-ids`。这不是经过审核的同义别名,而且会掩盖“单值/列表”和“记录触发模式”的理解错误。
2. `workflow history` 使用零基 `--page` 加 `--size`,不是 cursor/offset 分页,也不是通常理解的一基 `page-number`。
3. `--after-time/--before-time` 要求 Unix 毫秒。名称可以安全兼容 `start-time/end-time`,但别名层不能把 ISO 时间或秒级时间转换为毫秒。
4. 两个命令的 CLI 名称是 `--workflow-id`,服务 payload 分别使用 `workflowId`/`flowId`。`--flow-id` 可以作为同值 alias,但通用 `--id`、`--task-id` 不能自动归一。
本轮仍归为 11 类问题,在工作簿中展开为 428 条命令级表现,覆盖 202 个不同命令;相对旧报告新增 7 条,全部来自 `workflow run/history`。
## 2. 分析口径与数据来源
本报告不使用历史 badcase、`dws-eval` 结果或旧工作簿作为产品结论来源。事实优先级是:
```text
同提交 Runtime/Cobra Help
> 同提交 runtime-assembled Schema
> 当前 AITable Skill
> 最新正式 internal/cli/param_concepts.json
```
执行方式:
1. 固定最新 `origin/main@104eb715`;
2. 在独立 worktree 编译当前提交,避免使用终端中的旧 `./dws`;
3. 从同一命令树读取 Cobra Help 和运行时组装 Schema;
4. 将旧基线与最新基线按工具路径、参数名、类型和约束做机器 diff;
5. 读取当前 AITable Skill 和最新正式别名表;
6. 基于最新正式表生成完整候选,再生成代码并执行 CLI dry-run、block 和确认门禁测试。
当前 Schema 是声明驱动、运行时组装的结果,不依赖提交态 `schema_catalog.json`。本轮不会把 MCP 或 Skill 文本当成能够创建真实 CLI flag 的来源。
最终复核继续跟到 `origin/main@a5b9e5a1`。`104eb715..a5b9e5a1` 调整了 AITable record query 的有界分页、空页和精确 ID 校验实现,但没有新增或删除 `+record-query` 的公开 flag,也没有改变本报告涉及的 AITable 参数名称集合。最新 Skill 仍错误提到不存在的 `--all/--page-limit`,该实验结论单独记录在 `aitable_experiment_hallucination_analysis_20260817.md`。
## 3. 11 类参数问题
### 3.1 Base ID 名称跨命令不统一
最新版本中 190 个工具涉及单个 Base ID:186 个公开为 `--base-id`,4 个快捷命令公开为 `--base`。新增 `workflow run/history` 都使用 `--base-id`。
候选将 `base/base-id/base-token` 绑定到经过审核的 190 个精确命令。它们表示同一实体、同一角色、同一基数,值可原样传递;`source-base-id` 和 `target-base-id` 仍保持独立。
### 3.2 Table ID 缺少完整中央概念
最新版本有 108 个工具涉及单个 Table ID:106 个使用 `--table-id`,2 个快捷命令使用 `--table`。新增 `workflow run` 使用 `--table-id`,并要求它与 `--record-ids` 同时出现或同时不出现。
候选 `table_id` concept 只包含 `table/table-id`,继续排除:
- `table-ids`:多个数据表 ID;
- `source-table-id`、`target-table-id`:带业务角色;
- `name`:表名称,不是 ID。
### 3.3 搜索、时间边界与分页模型容易混用
旧问题包括 `query/keyword`、`limit/page-size/max-results` 和 cursor 同义拼写。新增 `workflow history` 又引入了独立分页模型:
```text
--after-time / --before-time Unix 毫秒
--page 从 0 开始
--size 每页条数,1..100,默认 20
```
安全处理分为两类:
- 可归一:`start-time→after-time`、`end-time→before-time`、`page-index→page`、`page-size→size`,值保持不变;
- 必须拦截:`cursor`、`offset`、`page-token`、`page-number`、`page-no`、`current-page`。它们的分页模型或值基不同,不能只改参数名。
### 3.4 业务参数与全局参数同名
9 个命令存在局部业务 flag 与全局 `format/fields/timeout` 同名的情况。中央 PreParse 无法仅凭参数名判断用户想表达业务字段还是输出控制。
候选仍只在 `aitable +export-data` 上增加单向 `export-format→format`;其他情况需要 CLI 命名、Help 或 Skill 治理。
### 3.5 单值、多值和来源/目标角色不能自动合并
受影响命令由 20 个增至 21 个。新增 `workflow run` 接受 1–5 个 `record-ids`,并与 `table-id` 成对出现。
实测最新正式逻辑中:
```text
--record-id R
↓ 未命中 reviewed semantic alias/block
↓ 通用参数名模糊纠错
↓ 静默改成 --record-ids R
```
候选为该命令明确 block `record-id` 和通用 `id`,不把单值隐式包装成列表,也不跳过原命令的成对和数量校验。
### 3.6 高置信度同义参数
受影响命令由 19 个增至 21 个:
- 7 个纯文本 `desc/description`;
- 4 个上传命令的字节数 `size/file-size`;
- 10 个工作流命令的 `workflow-id/flow-id`,包含新增的 run/history。
`size` 同时是分页概念,文件大小不能并入全局 `pagination_size`,仍使用命令级 scoped alias。
### 3.7 结构化载荷不是参数改名
5 个 field/form/record 命令包含文件读取、JSON 包装或多个 flag 合并。中央别名只改名称且保留原值,不能承接这些转换。
### 3.8 同义布尔参数类型不一致
8 个原生/快捷命令对 `enabled/hidden/required` 使用 string 或 bool 两种类型。问题在值解析和是否允许裸 flag,不在参数名称。
### 3.9 Schema 必填约束和示例漂移
旧有 16 个命令问题没有被新增 workflow 提交修复:7 个合法替代参数组被错误固定为 required,9 个已发布示例缺少真实必填参数。必须修改 leaf Contract/ParamDecl,别名表不能降低 required 或制造缺失值。
### 3.10 Skill 直接示例缺少必填参数
旧有 27 个命令仍存在直接示例缺少 `base-id/table-id` 等必填值的问题。本次 main 只新增和完善 workflow 文档,没有修复这些旧示例。
### 3.11 真实 `--view-id` 被接受但不生效
`record query/list` 仍接受隐藏 `--view-id`,但执行实现忽略它。因为它已经是真实 flag,unknown alias guard 不会接管;需要命令校验显式报错或真正实现 view 过滤。
## 4. 最新候选别名表
候选文件从当时最新正式 `internal/cli/param_concepts.json` 重新构建,不是在旧草稿上继续追加。本轮补齐最终 payload 模板后,AITable 增量已经同步到当前工作分支的正式表;文档目录中的候选继续保留,供和最新 main 合并时审计。
对 `origin/main@a5b9e5a1` 的结构化差分确认:候选的 metadata、schema version、其他 38 个 concept、全部非 AITable override 和全部非 AITable fixture 与最新 main 完全相同;差异严格收敛为下述 4 个扩展 concept、3 个新增 concept、8 个 AITable override 和 28 个 AITable fixture。因此后续合并应以文档候选向最新 main 应用 AITable 增量,不能用当前旧分支的整份正式表覆盖 main。
| AITable 增量 | 数量 |
|---|---:|
| 新增 concept | 3 |
| 扩展既有 concept | 4 |
| 新增 AITable command override | 8 |
| 新增 AITable validation fixture | 28 |
候选相对最新正式表只调整了以下 7 个 concept:
- 扩展:`base_id`、`search_query`、`pagination_size`、`page_cursor`;
- 新增:`table_id`、`plain_description`、`workflow_id`。
只增加了 8 个 AITable override,其中新增命令的核心规则是:
```json
"aitable workflow run": {
"block": ["record-id"],
"note": "单条 record-id 不自动变成 1–5 项 record-ids 列表"
}
"aitable workflow history": {
"scoped_aliases": {
"start-time": "after-time",
"end-time": "before-time",
"page-index": "page"
},
"block": [
"cursor", "offset", "page-token", "next-cursor", "next-token",
"page-no", "page-number", "current-page"
]
}
```
生成后的 AITable 规则面为:202 个命令、608 对 alias、554 条 blocked 拼写和 3 条 ambiguous。独立审计确认:非 AITable concept、override、fixture 与最新正式表没有漂移;旧草稿中的过期结构没有被带回。
## 5. 新增命令的最终链路验证
### 5.1 `workflow run`
canonical 与 alias dry-run 最终得到相同 payload:
```text
--base-id B --workflow-id W --table-id T --record-ids R
--base-token B --flow-id W --table T --record-ids R
↓
baseId=B, workflowId=W, tableId=T, recordIds=[R]
```
另外验证:
- `--record-id` 在进入 Runner 前返回 `blocked_flag`;
- 非交互环境不带 `--dry-run/--yes` 时返回 `confirmation_required`,没有越过写命令确认门禁。
### 5.2 `workflow history`
canonical 与 alias dry-run 最终得到相同 payload:
```text
--after-time 1000 --before-time 2000 --page 2 --size 25
--start-time 1000 --end-time 2000 --page-index 2 --page-size 25
↓
afterTime=1000, beforeTime=2000, page=2, size=25
```
`--cursor` 和 `--page-number` 均在进入 Runner 前返回 `blocked_flag`。
## 6. 验证结果与落地边界
补齐模板并在当前 `fix/param-hallucination` 工作分支替换正式表后:
| 验证 | 结果 |
|---|---|
| JSON/候选结构审计 | 通过 |
| `go generate ./internal/cli` | 通过 |
| `internal/cli`、`internal/pipeline`、alias generator | 通过 |
| `check-generated-drift.sh` | 通过 |
| `check-schema-catalog.sh` | 通过 |
| 新增命令代表行为 | 通过 |
| 原缺失的 10 个 complete-command 模板 | 已补齐 |
| 实验 badcase 新增 complete-command 模板 | 1 个(`+record-share-links`) |
| 新增代表 alias 最终 payload 等价 | 20 个 |
| `internal/app` 全量 | 通过(218.083s) |
补齐的完整命令模板是:
```text
aitable +find-record
aitable +record-share-links
aitable field search-options
aitable +workflow-list
aitable base list
aitable base update
aitable attachment upload
aitable workflow get
aitable +export-data
aitable workflow run
aitable workflow history
```
测试对每条 active fixture 先构造业务必填参数完整的 canonical 命令,再只替换一个 flag 拼写;代表集合继续执行到最终 transport capture,并比较 canonical 与 alias 的工具名和 payload。`list_workflows` 额外补了最小合法列表响应桩,避免结果投影在 payload 比较前因假响应结构不完整而失败。
## 7. 当前能力明确不能自动完成的事项
- cursor/offset/一基页码自动转换为零基 page;
- ISO 时间或秒级时间自动转换为 Unix 毫秒;
- 单个 `record-id` 自动包装为 `record-ids` 列表;
- source/target 角色猜测;
- 结构化 JSON、文件输入和多参数合并;
- required/示例/Skill 缺失值修复;
- 已存在但运行时被忽略的真实 flag 治理。
这些不是“候选遗漏”,而是参数名归一能力的边界。强行写入 alias 会隐藏真实的基数、角色、值基或执行语义差异。
## 8. 交付物
- `aitable_cli_param_hallucination_analysis_20260810.xlsx`:更新后的五页汇报工作簿;
- `param_concepts.json`:基于正式表重建的完整 AITable 候选;
- `aitable_experiment_hallucination_analysis_20260817.md`:两组实验的参数和 Shortcut 命令名幻觉复核;
- 本报告:最新产品变化、问题根因、候选设计、验证结果和正式落地前置条件。
@@ -0,0 +1,168 @@
# DWS AI 表格实验参数幻觉与 Shortcut 命令名幻觉复核
复核日期:2026-08-17
代码参考:`origin/main@a5b9e5a1`
实验来源:
- `/Users/hyz/works/data/aitable/raw-opencode`
- `/Users/hyz/works/data/aitable/eval_20260812_094400_aitable_v3_multi_aitable_run2_clean_c1/report_20260812_105618_rejudged.json`
## 1. 结论
两组实验暴露的是三类不同问题,不能全部写入 `param_concepts.json`:
1. `raw-opencode` 有 7 次不存在的 Shortcut 名称猜测,涉及 5 个错误名称;中央参数别名表只处理 flag 名称,不能修复命令路径。
2. `raw-opencode` 有一组可以安全由本轮 AITable 别名表处理的参数名不一致:例如 `+record-share-links --base-id/--table-id`。另有动态 `+resolve-context --base/--table`,但该命令不在当前仓库的可运行 Cobra 命令树中,正式生成器不允许为它生成中央 alias。
3. clean eval 没有发现新的 Shortcut 名称幻觉,但 5 个 case 反复使用 `+record-query --all`,其中 1 个 case 还使用 `--page-limit`。最新 CLI 没有这两个 flag,而最新 Skill 仍明确推荐它们,这是 Skill/CLI 契约漂移,不是中央别名表漏配。
本轮正式 AITable 参数表可以解决“同一业务对象、同一角色、同一基数、值无需转换”的名称差异;命令名猜测、缺少必填参数、结构化值格式错误和全量分页语义不应伪装成参数 alias。
## 2. 数据范围
### 2.1 raw-opencode
| 指标 | 数量 |
|---|---:|
| case | 61 |
| Bash 工具调用 | 331 |
| 以 `dws` 开头的调用 | 323 |
| 以 `dws aitable` 开头的调用 | 307 |
统计直接读取 61 个 `turn_001.jsonl` 中的 Bash tool use,不把 Skill 文本、Help 输出中的示例或命令输出里的字符串重复计为调用。
### 2.2 clean eval
| 指标 | 数量 |
|---|---:|
| case | 61 |
| 使用 Shortcut 的 case | 46 |
| 评测报告中的 `wrong_subcommand` | 10 |
| `wrong_skill` | 2 |
| `dependency_failed` | 12 |
| `param_mismatch` | 2 |
`param_mismatch` 不是参数幻觉的同义词。本报告回看了实际 `command_runs` 和输出;合法使用 `--filters` 代替评测器预期的 `--query`,或合法使用 `--query` 代替评测器预期的 `--record-ids/--field-ids`,不算未知参数。
## 3. raw-opencode:Shortcut 命令名幻觉
| 错误命令 | 次数 | case | 实际情况 | 处理方向 |
|---|---:|---|---|---|
| `aitable +field-create` | 2 | `0017`、`0053` | 不存在;正式入口是 `aitable field create` | 修 Skill/路由或增加经过审核的命令路径 fallback;不是参数 alias |
| `aitable +field` | 1 | `0046` | 不存在的 Shortcut Help 探测 | 不应兜底到某一个 field 子命令,保持拒绝 |
| `aitable +field-help` | 1 | `0013` | 不存在 | 不应创建“帮助类”业务 Shortcut |
| `aitable +table-create` | 1 | `0057` | 不存在;正式入口是 `aitable table create` | 可评估精确命令路径 fallback,不放入参数表 |
| `aitable +section-list` | 2 | `0005`、`0056` | 不存在;现有能力分为 `+section-list-empty` 和 `+section-list-nodes` | 语义不唯一,不能自动猜一个目标,应该修路由或要求消歧 |
合计 7 次、7 个 case。另有 `aitable dashboard list --base-id ...` 1 次:`dashboard` 下没有 `list` leaf,Cobra 落在父命令后把 `--base-id` 报为 unknown flag。这是原生命令路径幻觉,也不是参数名问题。
## 4. raw-opencode:参数与契约问题
### 4.1 当前正式候选可以安全兜底
| 原调用 | 正确参数 | case | 当前结论 |
|---|---|---|---|
| `+record-share-links --base-id ... --table-id ...` | `--base ... --table ...` | `0044` | 已由 `base_id`、`table_id` concept 覆盖;名称归一后仍需校验 `record-ids` 值格式 |
这条映射满足同一实体、同一角色、同一基数且值原样传递。它适合中央归一化;本轮已把 `base-id→base`、`table-id→table` 固化为两条 reviewed fixture,并通过 canonical/alias 最终 transport payload 等价测试。
### 4.2 已由 CLI 原生兼容解决,不再重复写 alias
| 原调用 | case | 当前 main 状态 |
|---|---|---|
| `table create --table-name ...` | `0057` | `table create` 已声明隐藏兼容 flag `--table-name`,执行时回退到 `--name` |
生成器会拒绝为真实存在的 native flag 再配置 semantic alias,这是正确约束。
### 4.3 参数名称看似可映射,但中央表当前无法落地
| 原调用 | 次数/case | 表面映射 | 不能落地的原因 |
|---|---|---|---|
| `+resolve-context --base ...` | 5:`0026`、`0028`、`0030`、`0040`、`0050` | `base→base-id` | `+resolve-context` 是实验环境提供的动态 Shortcut,不是当前仓库的 runnable Cobra leaf;生成器会拒绝悬空命令 |
| `+resolve-context --table ...` | 1:`0028` | `table→table-id` | 同上 |
如果该动态 Shortcut 要继续发布,应在它自己的声明中直接接受 native flag alias,或者先迁入 distribution-owned 命令树,再纳入中央 reviewed concept。不能在正式表里留一个当前发行版无法绑定的命令路径。
### 4.4 应保持拒绝,不应映射
| 错误调用 | case | 原因 |
|---|---|---|
| `+base-bootstrap --query ...` | `0009` | `query` 表示查找条件,而 `--name` 是新建 Base 名称;语义和副作用不同,不能做 `query→name` |
| `+field-update --type primaryDoc` | `0046` | 字段类型不可修改;把 `type` 映射到 `config` 或其他 flag 会隐藏不支持的业务动作 |
| `+resolve-context --include-dashboards` | `0005` | 命令没有该能力,不存在同义 canonical flag |
### 4.5 不是名称归一问题
| 类型 | 实际表现 | case | 为什么别名表不能解决 |
|---|---|---|---|
| 缺少必填目标 | `+resolve-context --include-fields` 未提供 table 目标 | `0030`、`0032`、`0040`、`0053`、`0056` | alias 不能凭空选择 table |
| 缺少必填目标 | `form list` 缺 `--table-id` | `0054` | alias 不生成业务 ID |
| 缺少必填参数 | `chart create` 缺 `--layout` | `0056` | alias 不能生成布局 JSON |
| 列表值格式错误 | `--record-ids '["R1","R2"]'`,命令要求 CSV/string-slice | `0044` | 当前能力只改参数名,不转换 JSON 数组为 CSV |
| 结构化值格式错误 | `field create --options "待联系,跟进中,已成交"`,实际要求 JSON 数组 | `0019` | 需要值解析/构造,不是名称同义词 |
| 结构化值格式错误 | `+record-query --filters '[...]'`,实际要求根对象 | `0032` | 需要修正 JSON 结构 |
| 枚举/能力错误 | `field create --type dateTime` 当前不支持 | `0020` | 不能靠改 flag 名解决值域错误 |
## 5. clean eval:真实参数幻觉
### 5.1 `+record-query --all`
在报告的 `command_runs` 中发现 12 个带 `--all` 的执行步骤,覆盖 5 个 case:
- `dws_aitable_0010`:8 次;
- `dws_aitable_0034`:1 次;
- `dws_aitable_0035`:1 次;
- `dws_aitable_0036`:1 次;
- `dws_aitable_0044`:1 次。
其中 11 次直接保留了 `unknown flag: --all` 输出;`0036` 的一次调用被 shell 管道/Python 后处理吞掉了 DWS 非零状态,最终表现成“0 条记录”,但根因仍是未知 flag。
### 5.2 `+record-query --page-limit`
`dws_aitable_0036` 有 4 个包含 `--page-limit` 的 command-run 步骤。Agent 在发现 `--all --page-limit 500` 失败后,又通过 shell/Python 循环继续用 `--page-limit 100` 重试,产生空结果和解析异常;读取 Help 后才改用真实的 `--limit/--cursor`。
### 5.3 根因是 Skill/CLI 漂移
最新 `origin/main@a5b9e5a1` 的 `+record-query` 公开参数只有:
```text
base-id, table-id, record-ids, field-ids,
filters, sort, query, limit, cursor
```
实现会在一次调用内按服务页聚合,最多返回 `--limit` 指定的 1–100 条;它没有 `--all` 或 `--page-limit`。但同一版本的 `skills/multi/dingtalk-aitable/SKILL.md` 仍写着:
```text
分页用 --cursor,全表用 --all --page-limit N
```
因此该问题会持续诱导模型生成无效参数。正确方案只能二选一:
1. 以当前 CLI 为准,立即把 Skill 改为 `--limit/--cursor`,并明确单次最多 100 条;需要更大范围时由调用方按 cursor 编排;
2. 如果产品确实承诺全量语义,在 `+record-query` leaf 上正式声明并实现 `--all/--page-limit`,补齐分页完整性、上限、Schema、Help 和测试。
不能在 `param_concepts.json` 中做 `all→某个 flag`,也不能把 `page-limit→limit`:前者没有 canonical 目标,后者会把“最大分页次数/全量预算”错误变成“最多返回记录数”,业务语义不同。
## 6. clean eval 中不应误判的两条
报告将 `0030`、`0031` 标为 `param_mismatch`,但实际命令使用的都是真实合法 flag:
- `0030` 用结构化 `--filters` 完成筛选,评测器期望 `--query`;
- `0031` 用 `--query` 搜索两个名称,评测器期望 `--record-ids/--field-ids`。
这两条是“调用形态没有精确匹配评测器模板”,不是 unknown flag,也不是参数名称幻觉。除非业务结果或完整性不满足任务要求,否则不应据此向别名表添加映射。
## 7. 对当前别名表草稿的影响
实验没有推翻本轮 AITable 草稿设计:
- `base_id`、`table_id` 可覆盖 `+record-share-links --base-id/--table-id`;
- `table create --table-name` 由 native compatibility flag 负责,不重复配置;
- 不把实验专属的动态 `+resolve-context` 路径写入正式表;
- 不为 `--all/--page-limit`、缺少必填参数、JSON 值转换或命令路径猜测制造虚假 alias。
需要单独进入后续治理的问题是:
1. 修复 AITable Skill 中 `+record-query --all --page-limit` 的错误指引;
2. 评估精确且无歧义的命令路径 fallback,例如 `+field-create→field create`、`+table-create→table create`;
3. `+section-list` 保持不自动修正,因为 `list-empty` 与 `list-nodes` 语义不同;
4. 若继续发布动态 `+resolve-context`,由该 Shortcut 自身声明 `base/table` 兼容,或先迁入正式命令树。
File diff suppressed because one or more lines are too long
@@ -0,0 +1,164 @@
# DWS Attendance 产品 CLI 参数幻觉分析
> 状态说明(2026-08-21):本文件保留 2026-08-11 的原始事实盘点和问题证据;其中 concept 数量、
> concept 名称及落位方案已被同目录 `attendance_cli_param_hallucination_review_20260821.md` 与最新候选
> `param_concepts.json` 取代,不应作为当前合入清单。最新候选基线为线上 main
> `11934eed057267d97e7442ddd420c711ee1802dc`。
## 1. 结论摘要
本报告以线上 `main` 提交 `fd24619437afcb92638d6a71e0bfd9254815fe06`(2026-08-11 14:08:11 +0800)为冻结基线,严格按照 `specs/product-cli-param-hallucination-analysis-spec.md` 对 Attendance(日程考勤)产品进行全量参数分析。事实来源只包括同一提交重新构建的 DWS、运行时组装 Schema、逐命令 `--help`、Attendance Skill、仓库内置 Shortcut、Cobra 隐藏兼容 flag 和必要实现代码;没有使用历史 badcase、`dws-eval`、`merged_scan.json`、历史工作簿或固定 Catalog。
冻结基线共有 57 个可执行 Attendance 工具,其中 19 个是仓库内置 Shortcut;52 个命令有业务参数,5 个命令没有业务参数。52 个有参命令累计出现 216 次业务参数,形成 97 个不同的公开 flag 名。逐 57 个 leaf 对账表明,公开 Help 与运行时 Schema 的业务 flag 集合差异为 0,说明本轮问题不是 Schema 生成滞后,而是产品内部的人员角色、单复数、时间语义、领域 ID、枚举结构,以及公开 camelCase 与隐藏兼容层的可发现性不统一。
本轮归纳出 7 类问题,参数问题明细共 120 行,覆盖全部 52 个有参命令;同一命令可以同时存在人员、时间、ID 和结构化值等多类风险,所以 120 行不能理解成 120 个命令。5 个无业务参数命令分别是 `attendance report columns`、`attendance +list-leave-types`、`attendance +my-attendance`、`attendance +this-month` 和 `attendance vacation types`,已完成盘点但无需进入参数别名治理。
优先级最高的问题是:
- `user/users/staff-ids/owner/target/operator-staff-id` 指向的人员角色和 cardinality 不同;
- `start/end/date/time` 的区间、单日和检查时刻语义不能只凭名称互换;
- 考勤组 ID、聊天会话 ID、班次 ID、规则 ID、审批计划 ID 和结果 ID 值域不同;
- `type/types`、`name/leave-names`、`class-id/classIds`、报表列 ID 与 JSON 列表存在单复数和结构差异;
- `schedule import` 公开使用 `--groupId/--scheduleVOS`,但隐藏接受 `--group-id/--schedules`,而 `--schedule-vos` 当前仍不能被中央链路兜底;
- JSON、枚举、单位、布尔开关和 `user-say-yes` 确认参数不能通过改 flag 名完成值转换或安全语义转换。
已在正式表的冻结副本上生成完整候选 `param_concepts.json`,没有替换当前工作区正式文件。候选新增 11 个 Attendance concept、仅扩展 7 个既有 concept 的 Attendance 命令范围,并新增 44 个精确 Attendance command override。生成后共有 52 个 Attendance 命令进入治理,形成 659 条 alias、370 条 block 和 6 条 ambiguous 名称项。
候选已通过结构化差异审核、生成器、24 组 alias/canonical 最终 dry-run payload 等价、5 组 block/ambiguous、原生隐藏兼容回归、`internal/cli`、`internal/pipeline`、`internal/app` 全包测试、生成漂移和 Schema 契约门禁。它仍是“待审核草稿”,因为本轮没有修改正式 `validation_fixture`,也没有把 24 组代表性测试转为仓库长期 complete-command payload 模板;正式替换前仍需补齐这些审核资产。
## 2. 产品参数现状
| 量化项 | 结果 | 说明 |
|---|---:|---|
| 可执行 Attendance 工具 | 57 | 同提交 runtime-assembled Schema 与官方命令树 |
| 仓库内置 Shortcut | 19 | 属于正式产品面,已纳入;不含用户自定义 Shortcut 和插件 |
| 有业务参数的命令 | 52 | 全部进入问题覆盖和候选影响审计 |
| 无业务参数的命令 | 5 | 已盘点,无需别名治理 |
| 业务参数出现次数 | 216 | 按 52 个有参工具累计 |
| 不同公开 flag 名 | 97 | 不含 root 全局输出类参数 |
| 使用 `--start` / `--end` | 17 / 17 个命令 | 时间范围最常见 |
| 使用 `--users` | 13 个命令 | 多人列表,不应与单人 `--user` 自动互转 |
| 使用 `--limit` | 10 个命令 | 与 page 或 offset 组成不同分页模型 |
| Help/Schema 公开参数差异 | 0 | 逐 57 个 leaf 使用同一冻结二进制核对 |
| 正式表已有 Attendance 覆盖 | 2 个命令 | 仅 `attendance check result` 与 Shortcut `+check-result` 的 `user_ids` |
## 3. 七类参数问题
### 3.1 人员标识符、单复数与操作角色混杂(30 个命令)
Attendance 同时使用 `user`、`users`、`staff-ids`、`operator-staff-id`、`owner`、`target`、`member`,群成员更新还将 add/remove、普通成员/额外成员、人员/部门拆成不同参数。它们都与人员或组织成员有关,但不是同一个业务角色,也不总是同一种 cardinality。
候选复用 `user_id` 与 `user_ids`,只在精确命令中绑定同角色同值域名称。例如 `record get --user-id → --user`、`vacation save-balance --target-user-id → --target`。`checkin records` 同时存在操作人单值和目标员工列表,因此只接受角色明确的 `--operator-user-id → --operator-staff-id`,通用 `--user/--user-id/--staff-id` 返回 ambiguous。`approve list --type` 会因单数/复数冲突被 block,`group update-members --users` 会因无法判断 add/remove 方向而 ambiguous。
### 3.2 时间范围、单日与检查时刻使用相似名称(22 个命令)
17 个命令使用 `start/end`,汇总和记录命令使用 `date`,`boss-check` 使用 `time`;`schedule get` 的 Skill 示例还使用原生隐藏兼容名 `workDateBegin/workDateEnd`。这些名称看起来都表示时间,但分别承担区间起止、单个工作日和检查时刻,值格式也可能不同。
候选仅在相同角色内允许 `begin/from/start-date/start-time → start` 和 `until/to/end-date/end-time → end`,并为统计日期补充 `work-date/query-date`。它不会自动把日期补成时间戳、推导缺失的区间端点或改变时区。
### 3.3 检索、筛选与分页参数命名分散(10 个命令)
检索主要使用 `query`,分页一部分使用 `page/limit`,打卡结果使用 `offset/limit`,群组检索还存在 `query-ble` 和 `query-position` 等专用过滤器。`cursor`、`offset` 和页码不是同一分页模型,通用文本 query 也不能替代蓝牙、地点等专用条件。
候选扩展 `search_query`、`page_number` 和 `pagination_size`;`offset` 仅在精确命令 override 中保护和归一,不新增中央 concept。只对 `keyword/current-page/page-size/page-offset` 等值原样名称做归一;不做 page↔offset/cursor 换算,也不把通用 query 合并到专用筛选参数。
### 3.4 考勤领域 ID 与作用域对象容易被泛化为通用 id(25 个命令)
补卡规则、加班规则、班次、考勤组、审批计划/结果、假期类型、配置场景和企业分别使用 `adjustment-id`、`overtime-id`、`class-id`、`group-id/groupId`、`plan-id/result-id`、`leave-code`、`setting-scene` 和 corp/scope 字段。
候选仅为可跨命令复用的班次、假期编码和查询日期保留中央 concept;补卡规则、加班规则、配置场景等局部角色下沉为精确命令 override。`rule-id` 只在已经确定规则类型的命令中绑定;`boss-check --id` 因可能指向 plan 或 result 而 ambiguous;Attendance 的 `group-id` 明确 block `conversation-id/open-conversation-id`,防止把聊天会话 ID 当成考勤组主键。
### 3.5 类型、名称与列表参数的业务含义和基数不同(20 个命令)
`type/types` 分别可能是单枚举和枚举列表;`name/leave-names` 分别是显示名称与假期名称列表;`columns` 是报表列 ID 列表;`classIds` 是结构化班次 ID 列表;`result`、`stats-type`、`unit` 等又是不同枚举。
审批类型、报表列和假期名称均按精确命令 scoped alias/block 处理,不新增中央 concept;只映射明确同角色且值可以原样传递的名称。单数/复数、编码/名称和 ID/JSON 列表之间不会自动转换。
### 3.6 公开 camelCase、隐藏兼容参数与 Skill 可发现性不一致(3 个命令)
`attendance schedule import` 的公开 Help/Schema 使用 `--groupId` 与 `--scheduleVOS`,Cobra 同时隐藏接受 `--group-id` 与 `--schedules`;`attendance group update` 公开使用 `--classIds`,格式归一可接受 `--class-ids`;`attendance schedule get` 的 Skill 使用隐藏 `--userIdList/--workDateBegin/--workDateEnd`。
这些隐藏参数已经由命令原生接受,候选不重复声明成中央 alias,并以最终 payload 回归测试证明兼容仍在。候选新增 `--groupid → --groupId` 与 `--schedule-records → --scheduleVOS`。但 `--schedule-vos` 当前仍报 `unknown_flag`:生成/PreParse 使用 Morph 后的名称作为键,而真实 Cobra flag 保留 acronym camelCase,现有模型无法同时表达“morphed 名与 canonical 形态不同”的这一个别名。该问题需要修改真实 flag 或完善生成/匹配模型,不应伪装成已解决。
### 3.7 结构化值、布尔开关、数值单位与确认参数不能靠改名转换(10 个命令)
`class-vo`、`group-vo`、`scheduleVOS`、`visibility-rules` 等接受 JSON;假期余额使用 `num`、`unit`、有效期和原因;全局/个人设置包含大量布尔或枚举字段;`user-say-yes` 是写操作确认参数。
当前链路只修改 argv flag 名并原样保留值,不能生成 JSON、修改 JSON 内部字段、翻译枚举、换算小时/天、拆分列表或改变确认语义。候选只增加 `class-json/group-json/schedule-records` 等不改变值结构的名称,且不把 `user-say-yes` 纳入 concept。
## 4. 候选别名表的实施方案
候选采取四层治理:
1. 扩展既有 concept:仅为 Attendance 命令扩展 `search_query`、`pagination_size`、`page_number`、`time_start`、`time_end`、`user_id` 和 `user_ids`。
2. 新增产品概念:分页 offset、补卡规则 ID、加班规则 ID、班次 ID、查询日期、假期编码、配置场景、报表列 ID、审批类型列表、排班记录 JSON 和假期名称列表。
3. 使用 44 个精确 command override:处理 owner/target/operator、add/remove、plan/result、group/class、设置字段以及稳定命令与 Shortcut 的差异,必要时使用 `scope_strict`、block 或 ambiguous。
4. 保留原生兼容:`group-id/schedules/userIdList/workDateBegin/workDateEnd/class-ids` 已由 Cobra 或格式归一接受,不重复建立中央来源。
生成后从正式表的 2 个 Attendance 命令、2 条 alias、12 条 block,扩展到 52 个命令、659 条 alias、370 条 block、6 条 ambiguous。数量看起来较大,是因为 concept 的成员、excludes 与精确命令范围会组合展开;不是 1035 个独立业务映射。人工审核重点放在人员 cardinality、操作角色、时间角色、领域 ID 值域、结构化值和安全 flag 六个边界上。
## 5. 当前能力无法解决或不应该解决的事项
- `attendance schedule import --schedule-vos`:当前 Morph/真实 camelCase flag 表达存在缺口,仍为 `unknown_flag`;安全做法是使用公开 `--scheduleVOS`、原生隐藏 `--schedules` 或候选 `--schedule-records`。
- 单人和多人列表互转:不能把 `user` 自动包装成 `users`,也不能从 users 中选择一个 owner/target/operator。
- 成员更新方向推断:通用 `--users` 无法判断 add/remove,也无法判断普通成员或 extra member。
- ID 查询与值域转换:不能把聊天会话 ID 转为考勤组 ID,也不能由名称查询 class/group/rule ID。
- 日期、时刻、时区和范围推导:不能补全时间、换算时区或根据单日自动生成 start/end。
- JSON、枚举和单位变换:不能构造 scheduleVOS、class-vo、group-vo、visibility-rules,不能翻译枚举或换算小时/天。
- 安全确认语义:`user-say-yes` 必须保持命令原生安全行为,不能通过参数 concept 猜测或绕过。
这些事项并不阻塞第一轮名称治理。可用 block/ambiguous 阻止明显错误;无法完成的是自动查询、角色选择和参数值变换。
## 6. 候选草稿审核结果
相对冻结基线正式 `internal/cli/param_concepts.json`:
- 新增 11 个 concept,全部只服务 Attendance 命令;
- 仅扩展 7 个既有 concept 的 `commands`,没有改 canonical、members、excludes 或非 Attendance 范围;
- 新增 44 个 Attendance command override;
- 没有删除或修改非 Attendance override;
- `validation_fixture` 完全未变;
- 当前真实工作区的正式 `internal/cli/param_concepts.json` 和 `param_aliases_generated.go` 均未修改。
候选初版在生成审核中发现并移除了原生隐藏 flag 的重复声明,包括 `approve/check/schedule` 中已有的 `to`、`user-id-list`、`work-date-begin` 与 `work-date-end`。最终候选明确区分了新增中央治理、原生兼容和当前不支持三类状态。
## 7. 验证结果
所有行为验证都在冻结 main 的 `/private/tmp` 隔离副本执行;写命令使用 `--dry-run`,未调用真实业务 API。
| 验证项 | 结果 |
|---|---|
| JSON 结构校验 | 通过 |
| `go generate ./internal/cli` | 通过,生成 331 个命令条目 |
| 候选作用域审计 | 通过:11 个新 concept、7 个既有 concept 扩展、44 个 override、非 Attendance 语义变化 0 |
| alias/canonical 最终 payload 等价 | 24 组通过,均为 `dry_run=true`、`executed=false` |
| block/ambiguous | 5 组通过,均在 dispatch 前终止 |
| 原生兼容回归 | `schedule get` 旧参数、`schedule import` 隐藏参数、`class-ids` 均通过 |
| 已知不支持边界 | `attendance schedule import --schedule-vos` 仍为 `unknown_flag`,与报告一致 |
| `go test ./internal/cli ./internal/pipeline -count=1` | 通过,77.436 秒 / 0.512 秒 |
| `go test ./internal/app -count=1` | 通过,244.942 秒 |
| `check-generated-drift.sh` | 通过,Schema 组装确定性通过 |
| `check-schema-catalog.sh` | 通过,27 个产品、1018 个工具 |
第一次在受限沙箱内运行 Schema 门禁时,`httptest` 因不能绑定 `[::1]` 中止;随后在允许本机回环端口的隔离环境中原样重跑整条门禁并退出 0。该环境性失败没有被计为代码通过,最终结论只依据完整成功的重跑结果。
候选仍不能直接替换正式表:正式落地前需要把关键 alias、block、ambiguous 和原生兼容用例补入仓库长期 `validation_fixture` / complete-command payload 测试,并由 Attendance 产品或接口负责人确认隐藏兼容参数是否继续保留、是否要统一 camelCase 公开 flag。
## 8. 第一轮改造建议
1. 先落地人员、时间、分页和专用 ID 的同角色同值域 alias,以及已经验证的 block/ambiguous。
2. 把 24 组最终 payload 等价和 5 组 guard 用例转成长期测试,再替换正式表。
3. 单独决策 `groupId/scheduleVOS/classIds`:如果修改真实 flag,应同步 Help、Schema、Skill 和兼容期;如果不修改,应在 Skill 明示 public canonical 与 hidden compatibility。
4. 不在第一轮处理 JSON、枚举、单位、ID 查询或确认参数值转换。
## 9. 可复用到其他产品的流程
冻结线上 main → 从同提交构建官方二进制 → 对账官方 Cobra、runtime Schema、Help、Skill 和内置 Shortcut → 按实体、角色、值域、cardinality、单位和结构聚合问题 → 基于冻结正式表生成完整候选 → 审核非目标 diff 与原生兼容重复 → 在隔离副本验证生成、最终 payload、保护和政策门禁 → 输出同口径 Markdown、五页中文 XLSX 与候选草稿。
## 10. 交付物
- 本报告:`docs/parameter-hallucination/attendance/attendance_cli_param_hallucination_analysis_20260811.md`
- 汇报工作簿:`docs/parameter-hallucination/attendance/attendance_cli_param_hallucination_analysis_20260811.xlsx`
- 完整候选别名表:`docs/parameter-hallucination/attendance/param_concepts.json`
工作簿固定包含“汇报总览、参数问题明细、兜底解决方案、当前无法解决、分析依据”五个中文页面,用于管理汇报、逐命令审核和后续落地跟踪。
@@ -0,0 +1,19 @@
# Attendance CLI 参数幻觉补充复核(2026-08-21)
基线为线上 `main` `11934eed057267d97e7442ddd420c711ee1802dc`。本目录独立候选已重建为
65 concepts / 394 overrides / 793 fixtures,并完成 fresh generate 与嵌入式 PreParse 验证。
本轮保留 52 个有实际生成差异的命令:新增 62 个 alias、312 个 block、21 个 ambiguous。
主要覆盖分页、人员 ID、班次/规则/请假码/日期角色;删除了没有生成效果的推测词和无法证明值域的
setting-scene 等概念。`page-number` 的共享词表已纳入独立候选,不再依赖别的产品先合并。
调整规则 ID、加班规则 ID、offset 和报表列 ID 均只覆盖同一逻辑端点的两条 CLI 路径,现已下沉为
精确命令 override;原有 alias 与 block 生成行为保持不变。
人员角色二次复核后进一步收紧:`attendance +get-checkin-record` 与 `attendance checkin records`
同时包含操作人单值和目标员工列表,通用 `user/user-id/userid/uid/staff-id` 无法唯一指向操作人,
现统一返回 ambiguous;仅 `operator-user-id/operator-id` 可归一到 `operator-staff-id`,
`target-user-ids/user-id-list` 可归一到 `staff-ids`。`attendance record get` 是单用户查询,复数
`users/user-ids` 保持 block,不再静默降为 `user`。
不能自动解决:日期格式、枚举翻译、复杂排班/审批 JSON、单复数 ID 值内容转换。旧版详细问题表仍见
`attendance_cli_param_hallucination_analysis_20260811.md`;本文件记录最新基线复核结论。
File diff suppressed because one or more lines are too long
@@ -0,0 +1,163 @@
# Audit 产品 CLI 参数幻觉分析
## 结论摘要
本分析以线上 `origin/main` 冻结提交
`aa4ae9a90323aa97e5cebdb5045b129f0f14e0df` 为唯一基线,使用该提交重新构建的
`dws`、运行时 Schema、Cobra Help、audit 实现与冻结正式
`internal/cli/param_concepts.json`。Audit 是本地 CLI 运维产品,现有 DingTalk Skills 没有
对应产品命令说明;该事实被记录为公开契约缺口,没有用其他业务 Skill 猜测参数。未使用固定
Catalog、历史 badcase、用户 Shortcut 或已安装插件,也没有修改当前工作区正式别名表。
Audit 有 3 个 Agent 可见叶:`audit tail`、`audit export`、`audit verify`。主要风险是把
tail 的最近记录数误写成分页参数,把 export 的日期边界误写成时间戳,把三个命令的输入文件、
输出文件与审计目录混成一个 `--file/--path`,以及忽略 `export --format` 与其他 audit 命令
全局 `--format` 的同名异义。冻结 Help 隐藏了真实全局 `--output`,但生成器能看到该 Cobra
flag;独立审核据此修正了初稿,最终候选精确扩围既有 `local_output_path`,没有错误拦截真实
输出能力。
最终候选已通过生成器、PreParse、4 组 alias/canonical 输出逐字节比较、3 组输出文件实际
落盘、12 组代表 block/ambiguous、原生参数、非目标结构恒等、`internal/cli`、
`internal/pipeline`、generated drift 与 Schema Catalog 政策。完整 `internal/app` 仅
complete-command 覆盖门禁未通过:缺 3 个 audit 模板,16 条 active fixture 尚未进入最终
payload 等价验证。正式状态为“规则与链路已验证,补 3 个模板后方可落地”。
## 参数问题
### 1. 最近记录数不是页大小、游标或文件大小
`audit tail --lines` 表示从最新审计文件尾部返回最近 N 条记录,要求正整数;它不是 page、
cursor、offset,也不是文件字节数。Agent 很容易沿用列表命令经验生成 `--limit`、`--count`、
`--page-size`。
候选只在 `audit tail` 上把 `limit/count/max-results/tail-lines` 原值映射到 `lines`;
`page/page-size/cursor/offset` block。它不把单值改成范围,不改单位,也不替命令修正零或负数。
### 2. export 的日期边界必须保留上下界角色和 YYYY-MM-DD 值域
`--since` 与 `--until` 是包含端点的文件日期边界,实现只移除连字符后比较
`audit-YYYYMMDD.jsonl` 文件名。通用 `start/end/from/to` 可能携带时间戳,裸 `--date` 又无法
判断是下界还是上界。
候选只允许角色与格式都明确的 `start-date/from-date → since`、
`end-date/to-date → until`,值完全不变;时间戳式名称、裸 date 和 time-min/time-max block。
现有别名表不能验证日期值,运行时也缺少严格 YYYY-MM-DD 校验,这应通过命令契约和实现修复。
### 3. 同名 `format` 在 audit 内有两种语义
`audit export --format` 是导出内容格式,只接受 `jsonl|csv`;`tail/verify` 的全局
`--format` 是 CLI 展示格式。把 output-format 全局映射到 export 的本地 format,可能把
`json/table` 误送给只接受 `jsonl/csv` 的参数。
候选仅在 export 上允许角色明确的 `export-format → format`,并 block `output-format`;
tail/verify 上 `export-format` block。真实 `--format` 始终保持原生,不能建立跨命令 concept。
### 4. 输入审计文件、输出文件和审计目录是三个不同角色
`verify --file` 是一个输入 JSONL 文件;tail 总是读取最新文件,export 根据日期范围选择文件;
三者都继承隐藏全局 `--output`,可把命令结果写入本地文件。审计源目录没有 CLI flag,只能由
`DWS_AUDIT_DIR` 环境配置。
候选在 verify 精确映射 `audit-file/audit-log-file/input-file/file-path → file`,在三个命令
精确扩围既有 `local_output_path`,使 `output-path/destination-path/save-path → output`。
`audit-dir/dir/directory` 与错误命令上的 input-file 角色 block;verify 的泛化
`path/log/audit-log/target` ambiguous。别名层不读取文件、不从目录选文件,也不创建审计链。
### 5. 全局真实 flag 不能被别名表伪装成产品参数
Help 展示 `--dry-run/--yes/--profile/--jq/--fields` 等全局 flag,另有隐藏真实
`--output`。Audit 三个叶都是本地只读,但这些 flags 仍属于真实 Cobra 面。生成器会拒绝对
真实 flag 配置 block,这次初稿对 `output` 的冲突正是在隔离生成阶段被捕获。
最终候选保持全部真实 flag 原生,只对非真实拼写做归一或保护。是否应在 audit Help/Schema
更清楚地区分本地参数、全局展示参数和隐藏输出能力,属于契约治理,不属于中央别名表。
### 6. Help、Schema 与 Skill 的公开契约仍有缺口
Help 与运行时 Schema 对 3 个叶和 5 个局部参数一致,但 Schema 参数 property 全由 flag 名
推断,export 日期没有严格格式约束;仓库安装的产品 Skills 中没有 audit 命令文档。同时
`--output` 是真实且有用的全局能力,却因隐藏而不在普通 Help 中展示。
候选不能补参数 description、日期校验、Skill 路由或 Schema 声明。第一轮别名落地可独立
进行,但应另行补 audit 本地产品说明、显式 ParamDecl/约束以及日期错误提示。
## 当前别名表可以实施的方案
1. 将 `audit tail/export/verify` 精确追加到既有 `local_output_path` 命令范围。
2. 为三个叶各建一个 `scope_strict` command override,不新增跨产品 concept。
3. 对行数、显式日期边界、导出格式和 verify 输入文件做原值映射。
4. 对分页、日期/时间戳混用、目录、错误输入角色和格式角色做 block/ambiguous。
5. 保持所有真实全局 flags 原生;补齐 3 个 complete-command payload 模板后再正式替换。
## 当前能力支持不了的事项
- 校验或修正 YYYY-MM-DD,或把时间戳截断成日期;
- 自动交换 since/until、判断边界先后或补默认日期;
- 把 output-format 的值从 json/table 转成 jsonl/csv;
- 从目录中选择最新/指定审计文件,或把多个文件合并成 verify 输入;
- 创建、修复或重新签名审计哈希链;
- 把 `--file` 同时解释成 verify 输入与输出目标;
- 通过参数表新增 `--audit-dir` 或把环境变量变成 CLI flag;
- 修改 Help/Schema description、日期约束或补建 Audit Skill;
- 自动补 `--yes`、`--dry-run` 或替用户选择输出格式;
- 在没有 complete-command 模板时直接替换正式表。
## 第一轮改造建议
第一轮建议落地 1 个既有 concept 的 3 命令精确扩围和 3 个作用域 override。落地 PR 必须为
`audit tail/export/verify` 补 complete-command E2E 模板,覆盖 16 条 active fixture,并用
隔离审计目录验证输入文件与全局输出文件两条角色链。日期格式校验、ParamDecl 与 Audit Skill
作为并行契约修复,不应塞进 alias 表。
## 候选 `param_concepts.json` 改动与审核
- 未新增 concept;既有 `local_output_path` 只追加 3 个精确 audit 命令;
- 新增 3 个 command override;新增 28 个 fixture,其中 16 个 active、12 个 guard;
- `go generate ./internal/cli` 从 569 个命令作用域变为 572 个;
- 生成结果新增 22 个 alias、41 个 blocked、4 个 ambiguous token;fallback 无变化;
- 非目标 concept、override、fixture 结构恒等;guard 与真实 flag 冲突为 0;
- 初稿把真实 `--output` 当成不可用目标,生成器拒绝后已审核修正为输出路径 concept;
- 4 组 alias/canonical 退出码、stdout、stderr 逐字节相同;3 组输出别名实际落盘成功;
- 12 组保护检查稳定返回 `blocked_flag` 或 `ambiguous_flag`;原生 `--lines` 正常。
候选位置:`docs/parameter-hallucination/audit/param_concepts.json`。
## 验证结果与正式替换前置条件
| 验证项 | 结果 | 说明 |
|---|---|---|
| JSON 解析与生成器 | 通过 | 572 个命令作用域 |
| PreParse 与 alias/canonical | 通过 | tail、export since、export until、verify 四组逐字节一致 |
| 输出文件语义 | 通过 | tail/export/verify 三个输出路径别名均真实落盘 |
| block/ambiguous | 通过 | 12 组代表错误均在派发前停止 |
| 原生参数 | 通过 | `--lines` 与隐藏真实 `--output` 保持原生 |
| 非目标回归 | 通过 | JSON 结构恒等;生成 diff 仅 3 个 audit entry;fallback 无变化 |
| `internal/cli`、`internal/pipeline` | 通过 | CLI 79.383 秒 |
| generated drift | 通过 | alias 与 Schema 双次装配确定 |
| Schema Catalog 政策 | 通过 | 28 产品、1166 工具;Runtime confirmation truth 通过 |
| 完整 `internal/app` | 未通过 | 298.359 秒;仅 complete-command 覆盖测试失败 |
| complete-command payload 门禁 | 未通过 | 200/203 个活跃命令有模板;缺 3 个命令、16 条 active fixture;395 active cases |
正式替换前必须补齐 3 个模板并重跑完整 `internal/app` 和政策门禁;未完成前,本候选只作为
完整待审核草稿。
## 分析依据
- 冻结提交:`aa4ae9a90323aa97e5cebdb5045b129f0f14e0df`,提交时间
2026-08-20 10:53:27 +08:00。
- 正式表 SHA-256:
`e41e7908c26dfdcc23636d27661d705416c001d67a6d0d5d658b1ca4bcc815c1`。
- 候选 SHA-256:
`bf038d49c355732f40f6e033d804f3f84581ae6dc431104d191d69fe970a28f3`。
- 命令实现:`internal/app/audit_command.go`;文件选择与哈希链在 `internal/audit`。
- Schema 来源:同一冻结二进制运行时声明组装;3 个本地可用工具,5 个局部参数。
- Skill:当前已安装 DingTalk Skills 无 Audit 产品命令定义;未用相邻 Skill 推断。
- 明确未使用:历史或固定 Catalog、实验 badcase/工作簿、用户 Shortcut、已安装插件。
## 可复用分析流程
冻结提交并重建二进制;以官方 Cobra 树盘点真实/隐藏 flags;逐叶对账 Help、完整 Schema、
Skill 与实现;按实体、角色、值域、单复数和单位审核;只允许值可原样传递的精确映射;把
输入/输出/目录和同名异义显式拆开;在隔离副本生成并检查真实 flag 冲突;执行 PreParse、
alias/canonical、保护、非目标回归、包测试与仓库政策;最后用 complete-command 最终 payload
门禁决定“可落地”还是“待补测试”。
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,227 @@
# DWS 日程 CLI 参数幻觉与参数契约分析
分析日期:2026-08-10
分析基线:`codex/fix-im-reliability@b243b38d650a93143ec39bba37441ffc4af802ed`
分析对象:DWS `calendar` 产品的正式原生命令、公开内置快捷命令、运行时 Schema、Cobra Help、日程 Skill、命令实现和当前中央参数别名表。
## 1. 结论摘要
日程产品的基础参数契约是一致的,但业务实体较多、同一实体在原生命令与快捷命令中换名明显,因此仍然容易诱发参数幻觉。本轮从固定提交重新构建临时二进制,盘点了 44 个公开可执行命令,其中包括 24 个原生命令和 20 个公开内置快捷命令。运行时 Schema 发布 41 个工具;`calendar acl add`、`calendar acl delete` 和 `calendar book update` 是有明确理由的 reviewed exclusions,仍属于公开可执行能力,已一并纳入分析。
对 41 个 Schema 工具逐条比较完整运行时 Schema 与可见 Cobra Help 后,参数名集合差异为 0。当前没有发现“Schema 发布了 CLI 不接受的可见参数”这种基础漂移。41 个 Schema 工具共有 134 次参数出现、40 个不同参数名;包含 reviewed exclusions 后,44 个公开命令中有 37 个带业务参数。
本轮识别 10 类聚合问题,在 Excel 中展开为 92 条命令级表现。风险主要集中在五组:
- 标识符角色:同一个 eventId 在 10 个命令中叫 `--event`、在 5 个命令中叫 `--id`;calendarId 多数叫 `--calendar-id`,日历本 get/update 又叫 `--id`。同一命令同时出现 eventId 与 calendarId 时,裸 `--id` 不能安全猜测。
- 人员值域:`--attendees/--users` 接收 userId 列表,`--open-dingtalk-ids` 是另一种标识符,`+book/+invite/+suggest-time --with` 和 `+free --who` 则接收姓名并进行解析。姓名和稳定 ID 不能仅改参数名互换。
- 会议室角色:`--rooms` 是 roomId 列表,`--room-name` 是搜索词,`--group-id` 是会议室分组 ID,`--location` 只是日程地点文本;四者不能混用。
- 时间与分页单位:多数 `start/end` 是 ISO-8601 时间点,`+free-slots --from/--to` 却是整数小时;`cursor`、零基 `pageIndex` 与相对日期 `in-days` 也不能通过改名互换。
- 结构化输入:`--files` 需要 `<fileId>:<name>`,循环日程需要成组 recurrence 参数,提醒是“开始前多少分钟”的 CSV;别名表不能自动补字段、组装 JSON 或做时间计算。
当前正式中央表只对 `calendar event list` 生成 1 条日程命令规则,形成 10 对 alias 和 3 条保护。大量原生命令另有隐藏 Cobra 兼容参数,但快捷命令与危险值域边界没有被统一治理。
本轮候选表新增 11 个 calendar concept,扩展 6 个已有 concept,并增加 26 条精确命令 override。候选生成后覆盖 28 个日程命令,形成 181 对 alias、210 条保护和 1 条 ambiguous;所有非日程生成规则保持逐字节一致。
候选已通过 JSON/生成器、`internal/cli`、`internal/pipeline`、generated drift、Schema assembly determinism 和 Schema policy,并完成两组最终 dry-run payload 等价验证和三组 dispatch 前保护验证。完整 `internal/app` 按仓库政策仍要求为 20 个新增活跃命令补齐 complete-command E2E 模板,涉及 39 个活跃 alias fixture。因此候选是“语义审核与生成链路已通过、可作为下一轮编码输入,但不能直接替换正式表”的草稿。
## 2. 分析基线与覆盖范围
本报告没有使用历史 badcase、`dws-eval`、评测 JSON 或历史实验工作簿作为产品事实来源。事实来自同一提交的当前产品面:
| 分析项 | 数量或结果 |
|---|---:|
| 公开可执行 calendar 命令 | 44 |
| 原生命令 | 24 |
| 公开内置 `+` shortcut | 20 |
| 运行时 Schema 工具 | 41 |
| reviewed Schema exclusions | 3 |
| Schema 参数出现次数 | 134 |
| 不同公开 Schema 参数名 | 40 |
| 带业务参数的公开命令 | 37 |
| Help/Schema 可见参数集合差异 | 0 |
| 正式表当前 calendar alias 命令 | 1 |
运行时 Schema 来源为 `runtime-assembled`,Catalog hash 为 `sha256:5d953fea6f9417039454c5d37b9c1e9f49189e9b56faa7627865e2403b0c8ebc`,surface hash 为 `sha256:936e55fe818f238b853752c7ed890eef22c4fd62545b04c9d9b0fdb280de9caf`。分析没有把提交态 Catalog、用户自定义 shortcut 或插件作为官方参数事实。
## 3. 主要参数问题
### 3.1 eventId 在 `event/id` 之间换名,裸 `id` 可能同时指向 calendarId
`+attendee-list`、`+cancel-event`、`+invite`、`+reschedule`、attachment/attendee/room 子资源命令使用 `--event`;`event get/delete/instances/respond/update` 使用 `--id`。这些值都属于 eventId,但参数名由命令形态决定。
原生命令已广泛注册隐藏 `event-id/eventId` 兼容参数,中央表不需要重复接管。候选新增 `calendar_event_id`,主要覆盖尚未具备同类兼容能力的快捷命令,并在事件角色唯一的 `+cancel-event/+invite/+reschedule` 上允许 `id → event`。
`+attendee-list` 同时有必填 `--event` 和可选 `--calendar-id`。此时裸 `--id` 有两个同等合理目标,候选将它标为 ambiguous,不静默选择。
### 3.2 calendarId 多数叫 `calendar-id`,日历本自身却使用 `id`
15 个 Schema 工具公开 `--calendar-id`,用于指定日历容器;`calendar book get` 和 reviewed exclusion `calendar book update` 则使用 `--id` 表示 calendarId。该 `id` 与 eventId、aclId、roomId 都不是一个值域。
原生日历命令已有隐藏 `calendar/calendarId` 兼容参数。候选新增 `calendar_book_id`,只在 `+agenda/+attendee-list` 等缺口上提供 `calendar/calendar-book-id → calendar-id`,并对可能混入 eventId、aclId、roomId 的拼写加保护。`+agenda --id` 被 block;`+attendee-list --id` 因双角色被 ambiguous。
### 3.3 人员姓名、userId 与 openDingTalkId 是三种不同输入域
`attendee add/delete/event create --attendees` 和 `busy search/event suggest/+freebusy --users` 接收稳定 userId 列表;`event create --open-dingtalk-ids` 接收另一类开放标识符。快捷命令 `+book/+invite/+suggest-time --with` 与 `+free --who` 接收姓名,并在命令内部进行人员解析。
候选分别建立姓名列表、单姓名和 ID 列表 concept,只在相同值域与基数内改名。`attendee-names → with` 可以成立,`user-ids → with` 不成立;`room/person name → users` 也不成立。后两类通过 block 引导模型选择 resolver 快捷命令或先查到稳定 ID。
### 3.4 roomId、roomName、room groupId 与 location 不能合并
`event create/room add/room delete/+freebusy/busy search --rooms` 接收 roomId 列表;`room search/+room-search --room-name` 是展示名搜索词;`room search --group-id` 是会议室分组 ID;`event create/update --location` 只是地点备注文本,不会预订会议室。
候选只允许 `room-ids → rooms`,以及在精确 `room search` 上允许 `room-group-id/group → group-id`、在 `+room-search` 上允许 `query/name → room-name`。roomName、roomId、groupId 和 location 之间全部保持保护边界。
### 3.5 `start/end` 是 ISO 时间,`+free-slots from/to` 是整数小时
14 个命令的 `--start/--end` 表达 ISO-8601 时间点,适合复用已有 `time_start/time_end` concept。`+free-slots --from/--to` 表达本地工作窗口小时,例如 `9` 和 `18`,同名 `from/to` 的值类型和单位已经不同。
候选把 ISO 时间同义名限制在经过审核的精确命令;`to → end` 使用精确 override,不加入全局 `time_end`,因为 `to` 在 `+free-slots` 上是整数。`+free-slots` 只允许 `start-hour/end-hour → from/to`,并阻止 ISO 时间拼写。
### 3.6 cursor、pageIndex、limit 不是一套可随意互换的分页参数
`event list/instances/+agenda` 使用 `cursor + limit`;`room search/list-groups/+room-groups` 使用零基 `pageIndex`(CLI 为 `page`)和 `limit`。`page` 不能改成 cursor,`cursor` 也不能转换为 pageIndex。
候选扩展已有 `pagination_size/page_cursor` 到经过审核的快捷命令,允许 `max-results/page-size/next-cursor` 等同值映射;`+room-groups` 只精确允许 `page-index → page`,并阻止 cursor/page-token。
### 3.7 标题、描述和搜索词存在 `title/summary/desc/query` 换名
事件创建/更新和 `+book` 使用 `--title`,calendar book update 使用 `--summary`;事件描述使用 `--desc`,book update 同样公开 `--desc` 但兼容 `description`;日历本搜索使用 `--query`,快捷命令同样表达名称关键词。
候选在 `+book` 建立 `summary/subject → title`,在 `+book-search` 扩展搜索同义名。原生命令已有 `summary/title`、`description/desc` 隐藏兼容时继续由命令自身处理。`rich-text-desc` 是 HTML 内容,不能与普通 `desc` 自动合并。
### 3.8 attachment、recurrence 与 reminder 需要结构化输入
`attachment add --files` 要求逗号分隔的 `<fileId>:<name>`;裸 `file-id` 缺少文件名,别名层无法补齐。循环日程的 recurrence 参数存在整组必填与类型相关约束;提醒 `--remind-minutes` 是相对开始时间的分钟偏移 CSV,不是绝对提醒时间。
候选只允许 `reminder-minutes → remind-minutes` 这种同单位映射,并阻止 `reminder-time/remind-at`。`file-id` 被 block,循环规则不提供任何“一个参数变完整规则”的 alias。
### 3.9 `status` 是参会响应,`free-busy` 是日程忙闲状态
`event respond --status` 的值域是 attendee response enum;`event create/update --free-busy` 的值域是 `busy/free`。二者都像“状态”,但业务对象、枚举和值的去向不同。
候选新增 `calendar_response_status`,只允许 `response-status/response → status`;`state/done/free-busy/availability` 在该命令上被保护。`event update` 仅允许拼写等价的 `freebusy → free-busy`。
### 3.10 duration、timezone 与相对日期只能做同单位同格式治理
`event suggest/+suggest-time --duration` 是分钟;`event create/suggest/update --timezone` 是 IANA 时区;`+conflicts/+free-slots --in-days` 是从今天起的整数天偏移。
候选允许 `duration-minutes → duration`、`tz/time-zone → timezone`、`day-offset/days-from-today → in-days`,但不做分钟与小时、时区与 UTC offset、相对天数与 ISO 日期之间的转换。
## 4. 当前别名表可以实施的方案
候选文件基于正式表完整复制后增加 calendar 改动,正式文件未修改。主要方案为:
1. 新增 `calendar_event_id`、`calendar_book_id`、`calendar_event_title`、`calendar_person_name_list`、`calendar_person_name`、`calendar_room_ids`、`calendar_duration_minutes`、`calendar_day_offset`、`calendar_reminder_minutes`、`calendar_response_status`、`calendar_timezone`;
2. 扩展已有 `search_query`、`pagination_size`、`page_cursor`、`user_ids`、`time_start`、`time_end` 的精确 calendar 命令范围;
3. 对 `id` 双角色、姓名/ID、会议室四类角色、ISO/小时、cursor/pageIndex、结构化 files 和提醒单位使用 block/ambiguous;
4. 保留原生命令已有隐藏 Cobra 兼容参数,不把同一兼容逻辑机械复制到中央表;
5. 所有自动 alias 只改参数名,参数值保持原样。
## 5. 当前能力支持不了的事项
- 从日程标题、日历本名称或用户描述查出 eventId/calendarId;需要读接口和唯一性判断。
- 姓名与 userId/openDingTalkId 互转;需要人员解析,且可能存在同名歧义。
- 会议室展示名、楼层文案或 groupId 转成 roomId;必须先执行 room search/list-groups。
- ISO-8601、Unix 毫秒、整数小时和相对天数之间转换。
- cursor 与零基 pageIndex 之间转换,或根据服务端返回生成下一页游标。
- 把裸 fileId 自动包装成 `<fileId>:<name>`,或替用户补文件名。
- 根据自然语言自动生成完整 recurrence pattern/range 参数组。
- 把绝对提醒时间换算成相对开始时间的分钟偏移。
- 对真实 flag 的错误值域做通用拦截,例如 `+book --with <userId>` 或 `room add --rooms <会议室名>`;真实参数名不会进入 unknown alias 兜底。
这些情况不是“别名表修不好一个错误参数名”,而是需要查询、值转换、结构组装或业务校验。正确做法是保持保护边界,并由 Help/Schema/Skill、resolver 或命令实现承担下一步。
## 6. Skill 与当前实现的漂移
本轮确认两处需要单独修正文档,不能用别名表掩盖:
1. 日程产品参考多处写明 `event list --max-results` 会被解析但丢弃。当前 `calendar.go` 实现实际上会读取该隐藏参数并写入最终 `limit`,因此“会被丢弃”的描述已经过期。
2. 会议最佳实践写明 `room search` 只有 `start/end/group-id/available`,并断言 `--query` 一定 unknown。当前公开 Help 还包含 `room-name/limit/page`;隐藏 `query` 可作为 `room-name` 的命令级兼容,隐藏 `available` 虽存在,但当前接口语义已经直接查询可用会议室。该最佳实践与当前产品参考和命令实现不一致。
`calendar participant ...` 是 `calendar attendee ...` 的 Cobra 路径别名,能够执行,但主 Schema 路径仍是 `attendee`;Skill 应优先展示主路径,避免 Agent 把命令路径 alias 与参数 alias 混为一层。
## 7. 候选别名表改动与审核结论
相对于正式表:
| 项目 | 正式表 | calendar 候选 | 变化 |
|---|---:|---:|---:|
| concept | 31 | 42 | +11 |
| command override | 128 | 154 | +26 |
| validation fixture | 253 | 312 | +59 |
| 生成 calendar 命令 | 1 | 28 | +27 |
| 生成 calendar alias | 10 | 181 | +171 |
| 生成 calendar block | 3 | 210 | +207 |
| 生成 calendar ambiguous | 0 | 1 | +1 |
独立审核结论:
- 所有 alias 目标都是同提交真实 Cobra flag;
- alias 不做查询、单位转换、列表拆合或 JSON 构造;
- `eventId/calendarId/aclId/roomId`、姓名/ID、roomName/roomId/groupId/location、ISO/小时和 cursor/pageIndex 均保留角色边界;
- `to` 没有加入全局 `time_end`,避免破坏 `+free-slots` 的整数小时含义;
- 生成前后所有非 calendar `ParamAliasEntry` 完全一致,SHA-256 均为 `30258faeef43a75bf4e5516993620e673b539ae40737e4b6129ed3b55b842c2b`;
- 210 条 block 只接管原本 unknown 的危险拼写,不覆盖真实 flag。
候选规模较大,正式落地前必须补齐最终 payload 模板,不能只凭生成器和 fixture 通过就替换正式表。
## 8. 候选验证结果
候选在独立临时源码副本中作为正式输入验证,未覆盖当前工作区的 `internal/cli/param_concepts.json`。
| 验证 | 结果 |
|---|---|
| `jq empty` / JSON Schema 读取 | 通过 |
| `go generate ./internal/cli` | 通过,生成 308 个命令条目 |
| `internal/cli` | 通过 |
| `internal/pipeline` | 通过 |
| generated drift + Schema assembly determinism | 通过 |
| Schema catalog policy | 通过:27 个产品、1018 个工具 |
| 非 calendar 生成规则对比 | 0 条变化,哈希一致 |
| 代表性 alias/canonical 最终 dry-run payload | 2/2 通过 |
| 代表性 block/ambiguous 在 dispatch 前终止 | 3/3 通过 |
| `internal/app` 全量 | 未全绿:20 个新增活跃命令缺 complete-command E2E 模板 |
两组最终 payload 等价验证为:
1. `event create --reminder-minutes --tz` 与 `--remind-minutes --timezone` 得到完全相同的 `summary/startDateTime/endDateTime/reminders/timeZone`;
2. `event suggest --duration-minutes --tz` 与 `--duration --timezone` 得到完全相同的 `attendeeUserIds/durationMinutes/timeZone`。
三组保护验证为:
- `+free-slots --start <ISO>`:以 `blocked_flag` 停止,避免 ISO 时间被解释成整数小时;
- `+attendee-list --id`:以 `ambiguous_flag` 停止,避免在 eventId 和 calendarId 中猜测;
- `attachment add --file-id`:以 `blocked_flag` 停止,避免把缺少文件名的值当作结构化 `files`。
需要补 complete-command 模板的 20 个命令为:
```text
calendar +agenda
calendar +attendee-list
calendar +book
calendar +book-search
calendar +cancel-event
calendar +conflicts
calendar +free
calendar +free-slots
calendar +freebusy
calendar +invite
calendar +my-free
calendar +reschedule
calendar +room-groups
calendar +room-search
calendar +suggest-time
calendar event create
calendar event respond
calendar event suggest
calendar event update
calendar room search
```
其中共有 39 个活跃 alias fixture 需要进入最终参数组装等价测试。候选当前应作为分析草稿和下一轮编码输入,不应直接替换正式表。
## 9. 第一轮实现边界
第一轮可优先落地 event/calendar 标识符、同格式时间、分页、duration/timezone 和搜索词等同值映射;同步加入 `id` 双角色、姓名/ID、会议室角色、时间单位、分页方式和结构化输入保护。补齐上述 20 个命令的最终 payload 模板后,再运行完整 app、policy 和跨产品回归。
本轮流程可直接复用于其他产品:固定提交并重建 → 合并运行时 Schema、Help、reviewed exclusions、公开 shortcut 与 Skill → 按实体/角色/值域/基数/单位审计 → 聚合问题 → 生成正式表完整副本 → 独立生成、行为等价、保护性拦截和非目标产品零影响验证。
@@ -0,0 +1,248 @@
# DWS 日程 CLI 参数幻觉与参数契约分析(最新版重审)
分析日期:2026-08-18
分析基线:`origin/main@7186a69b7821f1db0760f6d1bf606571939d95e1`
分析对象:DWS `calendar` 产品的原生命令、内置 Shortcut、运行时组装 Schema、Cobra Help、日程 Skill、命令实现与正式中央参数别名表。
## 1. 结论摘要
最新版日程产品的 Help 与运行时 Schema 基础契约是对齐的,但参数体系仍有明显的“同一实体多种命名、同名不同角色、相似名称不同值域”问题。尤其是 2026-08-17 新增的一组日程 Shortcut,使旧版分析不再完整:公开可执行命令从 44 个增至 52 个,内置 Shortcut 从 20 个增至 27 个;新增 `+create`、`+get`、`+room-find`、`+rsvp`、`+search-event`、`+suggestion`、`+update` 七个命令。
本轮从最新 `main` 重新构建独立二进制,盘点 52 个公开可执行命令,其中 25 个原生命令、27 个 Shortcut。运行时 Schema 发布 49 个工具;`calendar acl add`、`calendar acl delete`、`calendar book update` 是 3 个有明确理由的 reviewed exclusions,仍作为真实公开命令纳入分析。52 个命令共有 188 次业务参数出现、49 个不同公开参数名,45 个命令带业务参数。逐命令对比 Help 与完整 Schema leaf,公开参数名集合差异为 0。
问题不在 Schema 生成错误,而在跨命令的业务命名和角色差异:eventId 在 `--event/--id` 间切换,calendarId 多数叫 `--calendar-id`、日历本自身却用 `--id`;人员输入同时存在姓名、userId、openDingTalkId 和增加/移除两个方向;会议室同时存在 roomId、roomName、groupId 和纯文本 location;时间参数又混有事件时间、查询范围、整数小时、相对天数和分钟偏移。Agent 很容易把在一个命令学到的参数名迁移到另一个命令,或把看起来相似但值域不同的值原样传入。
当前正式中央表对日程生成的规则仍只有 `calendar event list` 一条命令,生成 10 对 alias 和 3 条保护;原生命令虽有较多隐藏兼容 flag,但新 Shortcut 基本没有中央语义兜底。日程 Skill 的可见 Shortcut 表只列出 21/27 个命令,漏掉 `+create`、`+get`、`+rsvp`、`+search-event`、`+suggestion`、`+update` 六个;会议室参考还在声明 `room search --query` 必然 unknown,与当前公开的 `--room-name` 以及 Shortcut `+room-find` 能力不一致。
本轮候选表基于最新正式表重新生成,而不是复用旧候选文件。独立审核后将仅服务一两个命令的单姓名、相对天数、提醒分钟和响应状态从 concept 下沉到精确 command override,最终只新增 7 个可稳定复用的日程 concept,扩展 7 个已有 concept,新增或修改 34 条日程 command override。候选覆盖 35 个日程命令,生成 291 对 alias、334 条 block 和 7 条 ambiguous。会议室复审确认 6 个真正消费 roomId 的命令全部接收列表,单个 roomId 是一元素列表,因此增加 6 条精确的 `room-id → rooms`,而 4 个原生命令已有的隐藏 `--room-ids/--roomIds` 仍由命令自身接管。`+book-search` 的 `--name` 在该精确命令中明确表达日历名称搜索词,因此由命令级 override 映射到 `--query`,不扩大到其他搜索命令。数量较大主要来自经过命令范围限制的时间、分页、标识符 concept 展开以及其 `excludes` 保护,并非 625 条手写独立判断。生成前后非日程规则逐字节一致。
候选已通过 JSON/生成器、`internal/cli`、`internal/pipeline`、全量 fixture、全量 guard、generated drift、Schema assembly determinism 和 Schema policy;新增七个 Shortcut 各抽取一个代表 alias,alias 与 canonical 到达相同最终 transport payload,7/7 通过。收敛后的四类命令级映射以及原生 `room-ids` 兼容又完成 7 组成对行为比较。会议室复审额外验证 6 个 `room-id → rooms` 和 4 个原生 `room-ids`,10/10 到达与 canonical 完全相同的最终 transport payload;`+book-search --name` 与 `--query` 的最终查询参数一致。完整 `internal/app` 唯一未通过的候选相关门禁是 complete-command E2E 覆盖:30 个新增活跃日程命令的 76 条 active fixture 尚未加入正式测试模板。因此候选结论是“业务语义与生成链路已审核,可作为编码输入;正式替换前必须补齐 payload 模板与代表性最终 payload 测试”,不是可以直接合并的正式表。
## 2. 分析基线与覆盖范围
| 分析项 | 数量或结果 |
|---|---:|
| 最新 `main` commit | `7186a69b7821f1db0760f6d1bf606571939d95e1` |
| 公开可执行 calendar 命令 | 52 |
| 原生命令 | 25 |
| 内置 Shortcut | 27 |
| 运行时 Schema 工具 | 49 |
| reviewed Schema exclusions | 3 |
| Help/Schema 参数出现次数 | 188 / 181 |
| 不同公开业务参数名 | 49 |
| 带业务参数的公开命令 | 45 |
| Help/Schema 可见参数集合差异 | 0 |
| Skill 已列 / 实际 Shortcut | 21 / 27 |
| 正式表当前生成 calendar 命令 | 1 |
运行时 Schema `source` 为 `runtime-assembled`,Catalog hash 为 `sha256:b79227d926ebff2882da91e114a3fa20380f488ea6dc86aaf6384cfdc5d14d23`,surface hash 为 `sha256:51b201c1115cf4829d276759267328aeaaf5b46d90579d9413dc244a1f8b15fc`;全仓库共组装 1149 个工具。
本报告没有把历史 badcase、`dws-eval`、历史工作簿、旧固定 Catalog、用户自定义 Shortcut 或插件作为产品事实来源。旧报告仅用于指出需要复查的方向,所有结论都回到本次同一提交的 Help、运行时 Schema、Skill 和实现重新确认。
## 3. 主要参数问题
### 3.1 eventId、calendarId 与宽泛 `id` 的命名和角色混杂
19 个命令用 `--event` 或 `--id` 表达 eventId;20 个命令公开 `--calendar-id`,而 `calendar book get/update --id` 的 `id` 表达 calendarId。`acl delete --acl-id` 又是第三种 ID。新 `+get/+rsvp/+update` 同时包含 `--event` 和 `--calendar-id`,此时裸 `--id` 有两个合理目标,不能静默选择。
候选用 `calendar_event_id` 与 `calendar_book_id` 分开治理,只在目标角色唯一时把 `event-id/calendar-event-id → event`、`calendar/calendar-book-id → calendar-id`;`+attendee-list/+get/+rsvp/+update --id` 标记为 ambiguous。`+cancel-event/+invite/+reschedule` 只有 eventId 一个标识符角色,允许精确的 `id → event`。原生命令已经接受的隐藏 `event-id/eventId/calendarId/calendar` 保持原生,不重复建立中央 alias。
### 3.2 姓名、userId、openDingTalkId、单值/列表及增删方向不能合并
`+book/+invite/+suggest-time --with` 和 `+free --who` 接收姓名并由 Shortcut 解析;`+create/attendee add/delete --attendees`、`+freebusy/+suggestion/busy search --users` 接收 userId 列表;`event create --open-dingtalk-ids` 是另一种标识符;`acl add --user` 是单个用户。新 `+update` 还把同一 userId 列表拆成 `--add-attendees` 与 `--remove-attendees` 两个相反角色。
候选分别保留姓名列表、单姓名和 userId 列表的值域。三个姓名列表 Shortcut 复用 `calendar_person_name_list`;仅 `+free` 使用的单姓名变体通过精确 override 处理,不为一个命令建立 concept。`attendee-names → with`、`name/person → who`、`user-ids → users`、`+create user-ids → attendees` 可以原样传值;姓名与 ID、单值与列表、userId 与 openDingTalkId 互转全部阻止。`+update add-user-ids/remove-user-ids` 可分别映射到方向明确的目标,但宽泛的 `attendees/users/user-ids` 被标为 ambiguous,避免猜测增加还是移除。
### 3.3 roomId、roomName、groupId 与 location 是四种不同角色
`--rooms` 是 roomId 列表,用于创建、查询忙闲或预订;`--room-name` 是会议室展示名搜索词;`--group-id` 是会议室分组;`--location` 只是日程地点文本,不会完成会议室预订。最新版 `+room-find` 同时提供时间、roomName、groupId 与分页,更容易让模型把已有 roomId 误当筛选条件。
6 个真正消费 roomId 的命令——`event create`、`room add`、`room delete`、`busy search`、`+create`、`+freebusy`——底层都接收 roomId 列表;单个 roomId 只是长度为 1 的列表。因此 `calendar_room_ids` 在这 6 个精确命令上允许 `room-id/room-ids → rooms`:其中 4 个原生命令已有真实隐藏 `--room-ids/--roomIds`,生成器对真实 flag 让路,只新增缺少的 `room-id → rooms`;两个 Shortcut 同时由中央 concept 补齐单复数拼写。在精确搜索命令上只允许 `query/name → room-name`、`room-group-id/group → group-id`。宽泛 `room` 仍被 block,因为它可能是会议室名称,不能不经搜索就作为 roomId;roomName、groupId、location 与 roomId 的值域边界继续保留。
### 3.4 事件时间、查询范围、整数小时与相对日期不能共用一套别名
多数 `start/end` 是 ISO-8601 时间,但语义分为“事件本身的开始/结束”和“查询窗口上下界”。`+free-slots --from/--to` 则是整数小时,`+conflicts/+free-slots --in-days` 是相对今天的天数。
候选只在查询范围命令使用通用 `time_start/time_end`;`+book/+create/+reschedule/+update` 等写命令只接受命令级 `from/begin/start-time → start` 与 `to/end-time → end`,不把 `since/time-min/start-date` 等范围或日期词套到事件时间上。`+free-slots` 只允许 `start-hour/end-hour → from/to`,并阻止 ISO 时间参数名。这是本次相对旧候选最重要的收敛之一。
### 3.5 cursor 与零基 pageIndex 是两种分页模型
`event list/instances/+agenda/+search-event` 使用 `cursor + limit`;`room search/list-groups/+room-find/+room-groups` 使用零基 `page + limit`。两种模型都出现“下一页”和“页码”的自然语言,但没有可逆转换关系。
候选复用 `pagination_size/page_cursor` 处理同值映射;页码命令只在精确命令上允许 `page-index → page`。cursor、page-token 与 pageIndex/offset 的交叉输入被 block,不把页码伪装成游标。
### 3.6 标题、普通描述、富文本描述和搜索词容易互相借名
事件标题在 `+book/+create/+update/event create/update` 中叫 `title`,日历本更新叫 `summary`;普通描述叫 `desc`,富文本另有 `rich-text-desc`;日历本搜索和事件搜索都叫 `query`,但事件搜索实际在当前页的标题、描述和地点中做全文匹配。
候选使用 `calendar_event_title` 处理 `summary/subject → title`,复用 `plain_description` 处理 `description → desc`,使用 `search_query` 处理 `keyword/q → query`。`+book-search` 的查询对象就是日历名称,因此在该精确命令上补充 `name → query`;这条映射不进入通用 concept,也不影响事件全文搜索。`rich-text-desc` 不映射到普通描述;`+search-event --title/--subject` 不映射到全文 `query`,因为这会把“只按标题”扩大成三字段搜索。
### 3.7 响应状态、忙闲状态和可用性不是同一种“状态”
`event respond --status` 的值域是 `needsAction/accepted/declined/tentative`;新 `+rsvp --status` 为了用户表达更自然,值域是 `needs-action/accept/decline/tentative`,命令内部再转换。`--free-busy` 是 `busy/free`,`+room-find --available` 是布尔筛选。
候选在 `event respond` 与 `+rsvp` 两个精确命令中分别处理参数名 `response-status/response → status`,不建立跨命令 concept,也不会翻译枚举值;`free-busy/state/done/availability` 在响应命令上被保护。跨命令把 `accepted` 改成 `accept` 或反向转换超出别名层能力,必须由命令自身或未来值归一模块处理。
### 3.8 duration、timezone、day offset 与 reminder 只能做同单位映射
`duration` 是分钟数,`timezone` 是 IANA 时区,`in-days` 是整数日偏移,`remind-minutes` 是相对日程开始时间的分钟列表。这些参数名常出现 `duration-minutes/tz/day-offset/reminder-minutes` 等合理变体。
候选允许同单位、同格式、值原样传递的映射;禁止分钟与小时、时区与 UTC offset、相对天数与 ISO 日期、绝对提醒时间与分钟偏移互转。
### 3.9 结构化输入和成组约束不能靠改名补全
`attachment add --files` 需要 `<fileId>:<name>`;循环日程的 `recurrence-*` 是成组约束;`+update` 的 start/end 必须一起出现,且同一 userId 不能同时增删。一个看似接近的参数名无法补齐缺失值、构造结构或满足组合约束。
候选对裸 `file-id`、不支持的 reminder/room 变更等使用 block,对 `+update` 的宽泛参会人输入使用 ambiguous;不提供“单参数生成完整 recurrence”或“自动补另一半时间”的 alias。
### 3.10 原生命令兼容 flag 与中央治理并存,必须明确边界
日程原生命令已经注册了多组隐藏兼容 flag,例如 `event get --event-id`、`event list --time-min`、`room search --query/page-index`、`book search --keyword`。这些是 Cobra 原生可接受参数,不应该被误写成中央别名能力。
候选保留原生行为,只补中央表尚未覆盖的 Shortcut 与危险边界。独立审核还移除了旧候选中重复的 `calendar event update freebusy → free-busy` 中央 override,因为当前命令自身已经发布隐藏 `--freebusy` 兼容入口。原生兼容与中央 alias 的测试、统计和文档应分开。
### 3.11 Skill 漏列六个新 Shortcut,会议室参考与当前命令漂移
当前 Skill 可见 Shortcut 表只覆盖 21/27,漏掉 `+create/+get/+rsvp/+search-event/+suggestion/+update`。此外 `references/03-meeting.md` 仍写 `room search` 合法参数“仅 start/end/group-id/available”,并断言 `--query` 一定 unknown;而当前公开 Help 已有 `room-name/limit/page`,命令自身也保留隐藏 `query → room-name` 兼容,且公开 Shortcut `+room-find` 已承担严格的可用会议室查询。
这不是别名表能够修复的问题,应修改 Skill 与会议室参考。候选表不会伪造或隐藏这类文档漂移。
## 4. 当前别名表可实施的方案
候选文件保存于同目录 `param_concepts.json`,是最新正式表的完整副本加 Calendar 改动,未修改 `internal/cli/param_concepts.json`。主要动作如下:
1. 新增 7 个稳定复用的日程 concept:`calendar_event_id`、`calendar_book_id`、`calendar_event_title`、`calendar_person_name_list`、`calendar_room_ids`、`calendar_duration_minutes`、`calendar_timezone`;单姓名、相对天数、提醒分钟和响应状态改为精确命令 override;
2. 仅向 7 个已有 concept 追加经过审核的日程命令:`search_query`、`pagination_size`、`page_cursor`、`plain_description`、`time_start`、`time_end`、`user_ids`;
3. 使用 34 条新增或修改的精确 override 处理 `id`、方向性参会人、会议室角色、事件时间、页码模型、局部同义词和结构化输入;
4. 目标唯一且值可原样传递时自动 alias;多目标用 ambiguous;不同值域、单位或角色用 block;
5. 保持命令自身已有的隐藏兼容参数为 native,不重复接管;
6. 不创建新的真实 flag,不修改值,不查询 ID,不拆分/合并列表,不绕过必填或确认。
## 5. 当前能力支持不了或不应该处理的事项
- 根据日程标题、日历本名称或自然语言定位唯一 eventId/calendarId;需要先查询并处理多候选。
- 姓名与 userId/openDingTalkId 互转;需要人员解析且可能同名。
- 根据会议室名、楼层、园区或 groupId 得到 roomId;必须执行会议室查询。
- ISO-8601、Unix 毫秒、整数小时、相对天数和分钟之间做单位或格式转换。
- cursor 与零基 pageIndex 互转,或凭空生成下一页游标。
- 把裸 fileId 包装成 `<fileId>:<name>`,自动补文件名。
- 从一个自然语言 recurrence 参数生成完整 pattern/range 参数组。
- 把绝对提醒时间换算成相对开始时间的分钟偏移。
- 在 `event respond` 与 `+rsvp` 之间翻译不同枚举拼写。
- 判断真实 canonical flag 中的值是否属于错误值域,例如 `--attendees` 实际填姓名、`--rooms` 实际填会议室展示名;unknown flag 兜底不会接管已经合法的参数名。
这些事项不阻止第一轮“参数名治理”,但必须保持保护边界,不能为了提高覆盖率强行做 alias。
## 6. 候选草稿改动与独立审核
| 项目 | 最新正式表 | 日程候选 | 变化 |
|---|---:|---:|---:|
| concept | 45 | 52 | +7 |
| command override | 208 | 242 | +34 |
| validation fixture | 425 | 531 | +106 |
| 生成 calendar 命令 | 1 | 35 | +34 |
| 生成 calendar alias | 10 | 291 | +281 |
| 生成 calendar block | 3 | 334 | +331 |
| 生成 calendar ambiguous | 0 | 7 | +7 |
独立审核结论:
- 每个 alias 目标都是同一提交该精确命令的真实 canonical flag;
- 自动映射均保持业务实体、角色、值域和单位一致,参数值原样传递;唯一的基数收敛是经过全命令盘点的单个 `room-id`,由命令原有列表解析器自然形成一元素 `roomIds`;
- `+get/+rsvp/+update` 的裸 `id`、`+update` 的宽泛参会人列表没有被强行选择;
- `+book/+create/+reschedule/+update` 没有复用包含 `since/time-min` 的范围时间 concept,只保留事件时间的命令级别名;
- `event respond` 与 `+rsvp` 的不同枚举使用各自命令级参数名映射,不建立跨命令 concept,也不进行值转换;
- 单姓名、相对天数、提醒分钟和响应状态四类局部规则已从 concept 下沉为 command override,生成的 alias/block/ambiguous 总量保持不变;
- 已移除对原生隐藏 `event update --freebusy` 的重复中央治理;`busy search/event create/room add/room delete --room-ids` 继续保持 native。四个命令进入 `calendar_room_ids` 的目的只是补 `room-id` 和统一保护边界,生成器不会为真实隐藏 flag 重复生成 alias;
- 291/334 的数量膨胀来自 35 个精确命令上 concept members/excludes 的确定性展开;抽样复核了新增七个 Shortcut 以及 alias/block 数量最高的 `+agenda/+create/+room-find/+search-event/+update`;`+book-search name → query` 只替换该命令原先对 `name` 的保护,不扩大作用域;
- 所有非 Calendar 生成规则逐字节一致,SHA-256 前后均为 `ab3dde6500fb2707b8b83e9babc8b92b595cc07ad44e4c25fee4b96fc2416503`。
审核状态:规则合理,但正式落地前仍需补测试;Skill 漏项和会议室参考漂移需另行修改;值转换与查询类场景暂不支持。
## 7. 候选验证结果
候选在独立临时副本中替换正式输入进行验证,没有覆盖当前工作区的正式 `internal/cli/param_concepts.json`。
| 验证 | 结果 |
|---|---|
| `jq empty` / `go generate ./internal/cli` | 通过,生成 569 个命令条目 |
| `internal/cli` | 通过 |
| `internal/pipeline` | 通过 |
| 全量参数 fixture 经嵌入表与 PreParse | 通过 |
| 全量 reviewed guard 到运行时契约 | 通过 |
| 代表性 guard 在 dispatch 前终止 | 通过 |
| 新增七个 Shortcut alias/canonical 最终 transport payload | 7/7 通过;另验证 `+book-search name → query` |
| 收敛规则与原生 room fallback 成对最终行为 | 7/7 输出逐字节一致 |
| `room-id → rooms` 最终 transport payload | 6/6 与 canonical 一致 |
| 四个原生命令隐藏 `room-ids` 最终 transport payload | 4/4 与 canonical 一致 |
| generated drift + Schema assembly determinism | 通过 |
| Schema catalog policy | 通过:27 个产品、1149 个工具 |
| 非 Calendar 生成规则 | 0 条变化,哈希一致 |
| 完整 `internal/app` | 未全绿:complete-command E2E 模板缺口 |
七个最终 payload 代表用例为:
- `+create summary → title`;
- `+get event-id → event`;
- `+room-find query → room-name`;
- `+rsvp event-id → event`;
- `+search-event keyword → query`;
- `+suggestion user-ids → users`;
- `+update event-id → event`。
完整 App 门禁要求为 30 个日程命令补 complete-command E2E 模板,共覆盖 76 条新增 active fixture;现有 `calendar event list` 模板已存在。缺模板命令为:
```text
calendar +agenda
calendar +attendee-list
calendar +book
calendar +book-search
calendar +cancel-event
calendar +conflicts
calendar +create
calendar +free
calendar +free-slots
calendar +freebusy
calendar +get
calendar +invite
calendar +my-free
calendar +reschedule
calendar +room-find
calendar +room-groups
calendar +room-search
calendar +rsvp
calendar +search-event
calendar +suggest-time
calendar +suggestion
calendar +update
calendar busy search
calendar event create
calendar event respond
calendar event suggest
calendar event update
calendar room add
calendar room delete
calendar room search
```
本轮补齐了此前清单遗漏的三个候选完整 argv 模板,并通过注入 Caller 验证其 `room-id → rooms` 最终 payload 等价:
```text
dws calendar busy search --rooms room-1 --start "2026-08-20T10:00:00+08:00" --end "2026-08-20T11:00:00+08:00"
dws calendar room add --event event-1 --rooms room-1
dws calendar room delete --event event-1 --rooms room-1
```
由于正式 `internal/cli/param_concepts.json` 尚未启用 Calendar 候选规则,当前不能只把这三条加入正式 `paramAliasCompleteCommands`:正式全量测试会把没有活跃正式 fixture 的模板判为多余。正式替换候选表时,应把 30 个模板与 76 条 active fixture 一次性接入;写命令必须使用注入 Caller/Runner 或 dry-run,不得发起真实业务调用。
## 8. 第一轮改造建议
1. 以本候选为语义基础补齐 30 个 complete-command 模板和代表性 payload 用例;其中本轮已补齐并验证此前遗漏的 3 个会议室命令模板;
2. 再次跑 `internal/cli`、`internal/pipeline`、完整 `internal/app`、generated drift 和 Schema policy;
3. 修改日程 Skill 可见 Shortcut 表,补齐六个新命令;同步修正会议室参考的合法参数和 `query/room-name` 说明;
4. 全绿后再把候选替换到正式 `internal/cli/param_concepts.json`,重新生成 `param_aliases_generated.go`;
5. 枚举值转换、ID 查询和真实 flag 值域校验作为后续能力,不与本轮参数名治理混合。
## 9. 可复用的产品分析流程
对其他产品继续使用同一流程:冻结最新提交并重建二进制;合并运行时 Schema、官方命令树和 reviewed exclusions;逐命令对账 Help/Schema/Skill;按实体、角色、值域、单复数和单位聚合问题;只为可原样传值的同义参数建立 alias;对多目标使用 ambiguous,对不同值域使用 block;候选基于最新正式表生成并保持非目标产品不变;最后通过生成、PreParse、payload、guard、Schema 与完整 App 门禁验证。
File diff suppressed because one or more lines are too long
@@ -0,0 +1,148 @@
# DWS Contact 产品 CLI 参数幻觉分析
> 状态说明(2026-08-21):本文件保留 2026-08-11 的原始事实盘点和问题证据;其中 concept 数量、
> concept 名称及落位方案已被同目录 `contact_cli_param_hallucination_review_20260821.md` 与最新候选
> `param_concepts.json` 取代。最新候选基线为线上 main `11934eed057267d97e7442ddd420c711ee1802dc`,
> 直属主管 userId 与账号昵称均按局部命令角色治理,不是中央 concept。
## 1. 结论摘要
本轮以线上 `main` 提交 `fd24619437afcb92638d6a71e0bfd9254815fe06`(2026-08-11 14:08:11 +0800)为冻结基线,从该提交重新构建二进制,并使用同一份官方 `NewSchemaSourceRootCommand()` 命令树,对 Contact 的真实 Help、运行时组装 Schema、仓库内置 Skill、审核排除项、隐藏兼容命令、命令实现和正式参数概念表进行对账。分析未使用历史 badcase、`dws-eval`、历史工作簿、固定 Catalog、用户自定义 Shortcut 或插件。
Contact 官方命令树共有 **72 个 runnable 路径**:其中 **46 个可见路径、26 个隐藏路径**。46 个可见路径由 38 个公开叶子和 8 个可执行父命令组成;38 个公开叶子中,35 个进入运行时 Agent Schema,`contact label list/get/list-members` 3 个公开叶子被 `schema_command_exclusions.go` 精确审核排除。产品共有 **16 个仓库内置 Shortcut 路径**,其中 14 个进入 Schema,`contact +get-roster` 与 `contact +list-roster-fields` 为隐藏内置 Shortcut。
官方树中有 **40 个路径带 canonical 业务参数**,共出现 **76 次 canonical 参数**、形成 **32 个不同 canonical flag 名**;其中 31 个是可见参数化叶子,9 个是隐藏参数化兼容路径。命令树另外注册了 **74 次隐藏 alias flag**,涉及 15 个隐藏名称。对 35 个 Schema 工具逐命令核对,真实 Help 与运行时 Schema 的公开业务 flag 差异为 **0**;Contact Skill 的 16 条机械扫描告警经人工复核均为命令别名、审核排除命令或正文路径提示,真实参数问题为 **0 条**。
本轮聚合为 **7 类参数问题**,Excel 中形成 **87 条“问题—命令”明细,覆盖全部 40 个参数化官方路径**,同时单独记录公开排除项和隐藏 Shortcut 边界。最需要优先处理的是:
- `--user-id`、`--staff-id`、`--ids` 和 `--master-user-id` 都是人员标识符,但分别代表目标员工、花名册员工、用户列表和直属主管,单复数及操作角色不能互换;
- `--dept`、`--parent`、`--depts` 和 `--dept-ids` 同时混合目标部门、父部门、CSV 列表与 JSON 部门成员数组,尤其同名 `--depts` 的值协议并不统一;
- `--query`、`--name`、`--org-user-name`、`--org-name`、`--creator-username`、`--nick` 和 `--ownness-text` 都可能被模型概括成“名称”,但目标实体和写入角色不同;
- 角色/标签命令使用宽泛 `--id`、`--label-id` 和名称列表 `--names`,必须把 role ID、用户 ID、部门 ID 与角色名列表分开;
- `--fields` 在 root 是输出字段筛选,在 `contact user profile get` 叶子内却是花名册 fieldCode 列表,当前作用域依赖参数位置;
- Schema、公开可执行面、隐藏 compatibility 和中央 alias 是不同层;解析成功不能证明中央规则生效,也不能证明最终业务参数被读取。
冻结正式别名表对 Contact 生成 **7 个命令条目、32 条 alias、29 条 block、0 条 ambiguous**。候选草稿扩展到 **31 个 Contact 命令条目、193 条 alias、159 条 block、7 条 ambiguous**,恰好覆盖全部 31 个公开参数化叶子。相对冻结正式表新增 15 个 Contact concept、扩展 8 个既有 concept 的 Contact 命令范围、新增 17 个 Contact override,并修改 1 个既有 Contact override;候选共有 21 个 Contact override。正式 253 条 validation fixture 保持不变,非 Contact concept、override 和保护规则均未修改。
候选已在隔离副本完成生成、构建、21 组 alias/canonical 最终 payload 等价、9 组 block/ambiguous、6 组原生兼容、4 组非目标产品回归以及 `internal/cli`、`internal/pipeline`、`internal/app`、生成漂移和 Schema 政策门禁。所有写命令测试均使用 `--dry-run`,没有真实业务写调用。候选可进入产品评审,但仍是完整待审核草稿,不会直接替换当前工作区正式 `internal/cli/param_concepts.json`。
## 2. 参数问题
### 2.1 用户标识符的名称、单复数和操作角色不统一
Contact 使用四类主要用户参数:目标员工 `--user-id`、花名册查询员工 `--staff-id`、批量用户 `--ids`、直属主管 `--master-user-id`。稳定命令和隐藏兼容入口还接受 `id`、`userid`、`user-id`、`user-ids` 等真实 alias。
这些值都可能长得像 userId,但业务角色不同。例如 `contact user update --user-id` 是被修改员工,`--master-user-id` 是该员工的直属主管;`contact user get --ids` 是列表,不能把单个 `--staff-id` 静默包装成列表。候选扩展 `user_id`/`user_ids`;主管 userId 仅在拥有 `--master-user-id` 的精确命令中按局部角色治理,并对单值/列表和目标/主管配置 block 或 ambiguous。
### 2.2 部门参数同时混用目标部门、父部门、CSV 列表和 JSON 数组
部门参数的复杂度来自三个维度同时存在:
- `--dept` 表示单个部门 ID,但 `contact +dept-members --dept` 又表示部门名称关键词;
- `--parent` 表示创建或移动后的父部门;
- `--depts` 在部门成员查询和离职查询中是 CSV 部门 ID 列表;
- `--depts` 在员工邀请/更新中是 JSON 部门成员数组;
- `--dept-ids` 在账号创建中是 CSV 列表。
候选只对同角色、同 cardinality、同值协议的名称做归一。CSV 列表复用 `dept_ids`;JSON 数组使用独立 concept,并只接受显式带 JSON 语义的来源名。`contact dept update/create` 使用 `target-dept-id`、`parent-dept-id` 等角色明确的 scoped alias;跨角色、跨单复数和 CSV/JSON 之间全部保护。
### 2.3 查询词、人员姓名、组织内姓名和对象名称共用 name/query 词根
`contact user/dept search` 的 `--query` 是检索词;`contact +lookup/+org/+team --name` 是人员姓名/花名查询;`--org-user-name` 是写入企业账号的员工姓名;`contact org create` 同时存在组织名 `--org-name` 和创建者名 `--creator-username`;`--nick` 与 `--ownness-text` 又分别是昵称和个人状态。
候选把这些角色拆成 search query、person-name query、org-user-name、organization name、creator name、nickname 和 status text。`employee-name`/`staff-name` 因为可表示“查询某人”或“写入组织内姓名”,没有放入全局 concept,而是在精确命令 override 中映射。`contact org create --name` 因有两个合理目标,明确返回 ambiguous。
### 2.4 角色标签的宽泛 id 与名称列表容易混淆
`contact +list-role-members` 和 `contact label list-members` 的真实 `--id` 表示单个 role/label ID;`contact label get --names` 表示逗号分隔的精确角色名称列表。隐藏兼容路径还可能出现 `--label-id`、`--role-id`、`--name`、`--query` 和 `--keyword`。
候选新增 role ID 与 role names 两个 concept,在精确命令中绑定宽泛真实 `--id`/`--names`,同时阻断部门 ID、用户 ID、ID 列表和名称列表之间的错误互换。角色名模糊搜索或名称转 ID 需要业务查询,不属于参数改名能力。
### 2.5 账号标识、联系方式和资料字段处于相邻工作流但值域不同
账号创建/更新和人员查询同时使用 `mobile`/`org-user-mobile`、`login-id`、`email`、`avatar-file-id`、`nick` 与 `org-user-name`。候选允许 phone/mobile-number 等在手机号角色内原样传递,但不把手机号转成 loginId/userId;email、头像文件 ID、昵称和组织内姓名分别使用独立 concept,并对 file/node/user/name 等跨值域名称做保护。
### 2.6 时间、分页和布尔控制缺少统一但不能进行值协议转换
`contact user dismission search` 使用 `start/end`、一基 `page` 和 `limit`;另有 `hide-retirement`、`hide-partner`。部门创建要求显式 `create-dept-group`,账号创建有 `send-pwd-via-sms`。
候选扩展 `time_start`、`time_end`、`page_number` 和 `pagination_size`,只在离职查询中接受同格式名称变体并原样传值;cursor/offset/page-token 等不同分页模型被保护。布尔 flag 保持 Cobra 原生解析和默认值,候选不转换其业务含义。
### 2.7 公开 Schema、审核排除项、隐藏兼容路径和同名 fields 的可发现性边界
Contact 的 72 个 runnable 路径并不都进入 Agent Schema。`contact label list/get/list-members` 是公开可执行叶子,但被精确审核排除;26 个隐藏路径用于兼容、别名或导航。有效隐藏 alias 保持原生,不在中央候选重复实现。
生成器审核还发现 `contact +get-roster` 虽是仓库内置隐藏 Shortcut,但当前参数 alias reducer 不把它识别为可治理的 runnable leaf,因此候选不能为它增加中央 alias,只能保留 canonical `--staff-id`/`--fields`。此外,`contact user profile get --fields` 在叶子后表示花名册 fieldCode,而 root `--fields` 表示输出筛选;别名表无法重命名真实同名 flag 或消除参数位置差异。
## 3. 当前别名表可以实施的方案
候选草稿位于同目录 `param_concepts.json`,是冻结正式表的完整副本加 Contact 改动,不是增量片段。
第一轮建议落地:
1. 扩展 `search_query`、`user_id`、`user_ids`、`dept_ids`、`time_start`、`time_end`、`page_number` 和 `pagination_size` 的 Contact 精确命令范围;
2. 新增手机号、按姓名查人、组织内员工姓名、JSON 部门数组、主管 userId、角色 ID/名称、花名册字段、头像文件 ID、状态文本、组织名、创建者名、登录号、邮箱和昵称等 15 个 concept;
3. 为 17 个新命令配置 override,并完善 `contact user profile get` 的既有 override;
4. 对用户单值/列表、目标员工/主管、部门单值/列表、CSV/JSON、目标部门/父部门、role/user/dept ID 和多种 name 角色配置 block/ambiguous;
5. 保留真实隐藏 flag 和兼容路径,不把原生行为重复包装成中央 alias;
6. 隐藏 `+get-roster`、Schema exclusions 和 root/leaf `fields` 冲突明确留在能力边界,不为了覆盖率强行配置。
候选生成后的影响面为 31 个 Contact 命令、193 条 alias、159 条 block、7 条 ambiguous。规则数量较大的主要原因是同一产品同时包含用户、部门、角色、账号、检索和资料字段,并需要对跨实体/角色/单复数做成对保护;所有新增 concept 和 override 均已由代表性最终 payload 或 guard 用例覆盖。
## 4. 当前能力支持不了或不应该做的事项
- 把姓名、花名或手机号自动查询并解析成唯一 userId;
- 把 CSV 部门 ID 列表包装成 JSON 部门成员数组,或反向转换;
- 把单个用户/部门 ID 自动变成列表,或从列表中选择一个;
- 根据宽泛 `name`、`department`、`manager` 在同一命令的多个目标间自动选择;
- 消除 root `--fields` 与花名册 leaf `--fields` 的真实同名冲突;
- 为当前 reducer 不识别的隐藏 `contact +get-roster` 增加中央 alias;
- 通过 `param_concepts.json` 让审核排除的 label 命令进入 Agent Schema;
- 对角色名称做模糊匹配并解析为 role ID;
- 在日期、page、cursor、offset、布尔默认值之间做值协议转换。
上述情况应继续使用真实 Help/Schema 的 canonical 参数;多目标场景由候选在 dispatch 前停止,查询/转换场景应先调用独立命令得到稳定值。
## 5. 候选草稿审核结论
候选相对冻结正式文件的结构化审核结论:
- 新增 15 个 concept,全部只包含 `contact ...` 命令;
- 修改 8 个既有 concept,只增加 Contact 精确命令范围,成员、排除项、含义和风险没有变化;
- 新增 17 个 override,修改 1 个既有 Contact override;候选共有 21 个 Contact override;
- 非 Contact concept、override、保护规则和 253 条 validation fixture 变化为 0;
- 所有可治理命令路径都来自同提交官方命令树;
- `employee-name`/`staff-name`、target/parent department 等存在角色冲突的来源已从全局 concept 移到精确 override;
- 与真实隐藏 flag 重复的来源由生成器自动保留为原生行为,没有重复 alias;
- `contact +get-roster` 因 reducer 边界从候选移除,并转入当前无法解决;
- 所有自动 alias 均满足同实体、同角色、同值域、同单位、同 cardinality 且值可原样传递;
- 无法确认的映射均转为 block、ambiguous、原生行为或当前不支持。
因此候选在语义、作用域和仓库契约上合理,可进入 Contact owner 评审;它没有直接修改正式工作区别名表。
## 6. 验证结果
候选在隔离副本中临时替换正式输入并重新生成、构建和测试,验证包括:
- `jq` 解析、真实生成器读取和二次生成确定性;
- 21 组 alias/canonical 最终等价,其中 17 组真实 dry-run payload、4 组 Shortcut mock 结果/错误等价;
- 9 组 block/ambiguous 在 dispatch 前停止;
- 6 组原生兼容,其中 dept-id/id/user-id/keyword/role-id 最终等价,隐藏 `+get-roster` canonical mock 可执行;
- 4 组 Calendar、Doc、Drive、Mail 非目标 alias/canonical 最终 payload 等价;
- `internal/cli` 81.404 秒、`internal/pipeline` 0.482 秒、`internal/app` 242.021 秒全部通过;
- `check-generated-drift.sh` 通过,两次生成确定;
- `check-schema-catalog.sh` 通过,最终仍为 27 个产品、1018 个工具。
本产品不需要修改 validation fixture 或 complete-command payload 模板。写操作全部使用 `--dry-run`,未发起真实业务写调用。当前工作区正式 `internal/cli/param_concepts.json` 和 `internal/cli/param_aliases_generated.go` 在验证前后均未修改。
## 7. 第一轮改造建议
1. 优先落地用户角色、部门值协议、role ID/name 和多目标 name 保护,避免写命令只增加 alias 而没有风险约束;
2. 同步落地 21 组 payload 等价、9 组 guard 和 6 组原生兼容回归,防止后续命令新增时作用域静默扩大;
3. 单独评审 `contact label ...` 的 Schema exclusions 是否仍应保留,这属于 Agent 可发现性治理,不混入别名表改动;
4. 评估花名册业务 `--fields` 是否需要长期重命名,并为 root/leaf 参数位置给出明确文档;
5. 如果未来希望治理隐藏 `+get-roster`,先完善 reducer 对官方隐藏内置 Shortcut 的审核入口,再新增规则。
## 8. 可复用分析流程
后续产品继续使用同一流程:冻结提交并构建官方二进制 → 合并 runtime Schema、真实 Help、Skill、官方树、审核排除项和内置 Shortcut → 按实体、值域、角色、cardinality、单位和值协议归并问题 → 基于冻结正式表生成完整候选 → 审核真实 flag 冲突和原生 compatibility → 在隔离副本执行生成、PreParse、payload、保护、非目标回归、包测试和政策门禁 → 只交付产品 Markdown、五页中文 Excel 和候选草稿,不直接改正式别名表。
@@ -0,0 +1,9 @@
# Contact CLI 参数幻觉补充复核(2026-08-21)
独立候选已按线上 `main` `11934eed057267d97e7442ddd420c711ee1802dc` 重建为
66 concepts / 367 overrides / 728 fixtures,fresh generate 与嵌入式 PreParse 通过。
25 个命令产生有效差异:28 alias、116 block、7 ambiguous。只保留姓名查询、单用户 ID、部门 JSON、
等可复用且可证明的中央角色;昵称和主管用户 ID 是局部 flag 角色,已下沉到精确命令 override。
同时移除无生成效果的 phone/mobile-number 猜测、`avatar-file` 等伪别名。
完整手机号反查与姓名语义搜索仍按 Contact/AISearch 产品边界执行,不由参数名兜底互相代替。
File diff suppressed because one or more lines are too long
@@ -0,0 +1,147 @@
# CLI 参数幻觉分析跨产品汇总与落地状态
## 总结
本轮严格以线上 `origin/main` 冻结提交
`aa4ae9a90323aa97e5cebdb5045b129f0f14e0df`(2026-08-20 10:53:27 +08:00)为统一事实
基线,依次完成 report、ding、event、markdown、aisearch、whiteboard、audit、pat、devdoc、
live、mcp、hrbrain 共 12 个产品的 CLI 参数幻觉分析。
每个产品均已在 `docs/parameter-hallucination/<product>/` 交付:
1. 独立中文 Markdown 分析报告;
2. 含“汇报总览、参数问题明细、兜底解决方案、当前无法解决、分析依据”五个中文工作表的 XLSX;
3. 基于冻结正式 `internal/cli/param_concepts.json` 的完整独立候选草稿。
共交付 36 个产品文件。12 份 XLSX 均核验为 5 个工作表、5 张结构化表,逐页完成视觉检查,
公式错误扫描为 0;所有检查 sidecar 已清理。
冻结正式表 SHA-256 为
`e41e7908c26dfdcc23636d27661d705416c001d67a6d0d5d658b1ca4bcc815c1`。全部生成、PreParse、
alias/canonical、block/ambiguous、非目标回归和仓库政策检查均在隔离 worktree
`/private/tmp/dws-param-analysis-aa4ae9a90323` 中进行。隔离副本最终恢复到冻结提交,`git status`
为空,正式输入与生成文件无 diff。
当前工作区在任务期间由其他工作推进到
`3d7ab2690c3bce837a1d4344ca92e4469a3df801`;当前正式表 SHA-256 为
`5e02331b579fe7392ef642dcb030a253e4fd4388afa49e5866ecd654bea9979f`。本轮没有修改、覆盖或回退
当前工作区的正式 `internal/cli/param_concepts.json`。
## 落地状态总览
| 产品 | 候选改动规模 | 隔离验证结论 | 落地状态 | 正式落地前置 |
|---|---|---|---|---|
| report | 3 个既有 concept 扩围;16 个 override;15 个 fixture | 生成、PreParse、payload、保护、回归、drift、Schema 政策通过 | 条件通过 | 补 7 个 complete-command 模板;复核 Skill 发送人值域 |
| ding | 7 个既有 concept 扩围;11 个 override;15 个 fixture | 运行链路与政策通过 | 条件通过 | 补 6 个模板;若治理 `+send-by-message`,先补 Contract/Identity 使其进入 reviewed source tree |
| event | 3 个新增 + 3 个既有 concept;6 个 override;30 个 fixture(22 active) | 生成、5 组 payload、保护、回归与政策通过 | 条件通过 | 补 6 个 complete-command 模板 |
| markdown | 5 个新增 + 6 个既有 concept;5 个 override;35 个 fixture(24/11) | 生成、5 组 payload、11 组保护、回归与政策通过 | 条件通过 | 补 5 个模板;补 Skill 的 diff Usage/Flags/Examples |
| aisearch | 7 个新增 concept;4 个 override;30 个 fixture(13/17) | 生成、4 组 payload、保护、回归与政策通过 | 条件通过 | 补 4 个模板;另审父命令/路径 alias 与 hidden 兼容面 |
| whiteboard | 2 个新增 + 1 个既有 concept;2 个 override;20 个 fixture(8/12) | 临时修复 fresh-tree 可见性后全部规则与政策检查通过 | 基线阻断 | 先修 declaration-only 生成树可见性,再补 2 个模板并全量复验 |
| audit | 1 个既有 concept 扩围;3 个 override;28 个 fixture(16/12) | 生成、payload、文件落盘、保护、回归与政策通过 | 条件通过 | 补 3 个 complete-command 模板;Audit Skill 缺失作为独立契约事项 |
| pat | 0 个 concept;2 个 override;28 个 fixture(14/14) | 候选规则与政策通过;另发现 dry-run 仍写本地策略 | 条件通过 | 补 2 个模板;单独修复并回归 browser-policy dry-run Runtime 契约 |
| devdoc | 2 个既有 concept 扩围;1 个 override;17 个 fixture(8/9) | 包括完整 `internal/app` 295.860 秒在内全部通过 | 可进入落地评审 | Help/Skill 默认输出与必填描述、非法数字/双入口行为作为独立后续 |
| live | 0 个 concept/alias;1 个纯保护 override;16 个 guard | 包括完整 `internal/app` 239.904 秒在内全部通过 | 可进入落地评审 | 无 active fixture 模板依赖;保持零业务参数边界 |
| mcp | 0 个 concept/alias;1 个纯保护 override;16 个 guard | 包括完整 `internal/app` 250.336 秒在内全部通过 | 可进入落地评审 | 无 active fixture 模板依赖;保持位置参数和敏感输出边界 |
| hrbrain | 3 个新增 + 3 个既有 concept;11 个 override;59 个 fixture(31/28) | 候选规则全过;临时补 10 个模板后完整 `internal/app` 237.773 秒通过 | 条件通过(已证明配套方案) | 候选与 10 个 complete-command 模板同一变更落地 |
状态统计:3 个候选已通过现有完整应用门禁,可直接进入正式落地评审;8 个候选的规则和运行链路
已通过,但需补 complete-command 模板或配套契约修复;1 个 Whiteboard 候选还受冻结基线
fresh-tree 可见性缺陷阻断。若 12 个产品一次性合并,新增模板前置合计为 45 个不同命令;Devdoc
使用既有模板,不计入新增数量。
## 交付索引与候选哈希
| 产品 | Markdown | 五页 XLSX | 完整候选 | 候选 SHA-256 |
|---|---|---|---|---|
| report | [报告](report/report_cli_param_hallucination_analysis_20260820.md) | [工作簿](report/report_cli_param_hallucination_analysis_20260820.xlsx) | [候选](report/param_concepts.json) | `543dfafca84535090c1fa29178c45c27eea42470713ea7a6816c884716c2552a` |
| ding | [报告](ding/ding_cli_param_hallucination_analysis_20260820.md) | [工作簿](ding/ding_cli_param_hallucination_analysis_20260820.xlsx) | [候选](ding/param_concepts.json) | `382e6c16c913b78bf325cf7193cca32eb8b035439af970b9df58735b133e398c` |
| event | [报告](event/event_cli_param_hallucination_analysis_20260820.md) | [工作簿](event/event_cli_param_hallucination_analysis_20260820.xlsx) | [候选](event/param_concepts.json) | `b3d0a0ddb6fa067222742b96c28130f44a62df85e7b55d699497f15ff0e39d36` |
| markdown | [报告](markdown/markdown_cli_param_hallucination_analysis_20260820.md) | [工作簿](markdown/markdown_cli_param_hallucination_analysis_20260820.xlsx) | [候选](markdown/param_concepts.json) | `7df963c21af2d92c24b2a65e9110f990d97fd575eeded01962f31a3fc0458c6f` |
| aisearch | [报告](aisearch/aisearch_cli_param_hallucination_analysis_20260820.md) | [工作簿](aisearch/aisearch_cli_param_hallucination_analysis_20260820.xlsx) | [候选](aisearch/param_concepts.json) | `1742e69d628c3f99945201acc9cd1a46fb7b93a1e080a9f19294a87a97eba514` |
| whiteboard | [报告](whiteboard/whiteboard_cli_param_hallucination_analysis_20260820.md) | [工作簿](whiteboard/whiteboard_cli_param_hallucination_analysis_20260820.xlsx) | [候选](whiteboard/param_concepts.json) | `3a45256af6fe3773a7a944463a5027003a2d25dab8b7e8613f02dcf5435dfba8` |
| audit | [报告](audit/audit_cli_param_hallucination_analysis_20260820.md) | [工作簿](audit/audit_cli_param_hallucination_analysis_20260820.xlsx) | [候选](audit/param_concepts.json) | `bf038d49c355732f40f6e033d804f3f84581ae6dc431104d191d69fe970a28f3` |
| pat | [报告](pat/pat_cli_param_hallucination_analysis_20260820.md) | [工作簿](pat/pat_cli_param_hallucination_analysis_20260820.xlsx) | [候选](pat/param_concepts.json) | `d09712be8b70294c08a67c7977d9e8f028be51777b6f9563ac51e2d87d37ea03` |
| devdoc | [报告](devdoc/devdoc_cli_param_hallucination_analysis_20260820.md) | [工作簿](devdoc/devdoc_cli_param_hallucination_analysis_20260820.xlsx) | [候选](devdoc/param_concepts.json) | `1aa25a72e583aca9853beb262316e8e11543fcde0e0b2dc59ccd070484b78b82` |
| live | [报告](live/live_cli_param_hallucination_analysis_20260820.md) | [工作簿](live/live_cli_param_hallucination_analysis_20260820.xlsx) | [候选](live/param_concepts.json) | `842d3a7f04073962622ce0ab23c8b5cf22a3f5b771917f9d0765219060d54d1b` |
| mcp | [报告](mcp/mcp_cli_param_hallucination_analysis_20260820.md) | [工作簿](mcp/mcp_cli_param_hallucination_analysis_20260820.xlsx) | [候选](mcp/param_concepts.json) | `81a99ee95f4b4da06752b13d813fa2c555df2510fcb4239e772c69a947754482` |
| hrbrain | [报告](hrbrain/hrbrain_cli_param_hallucination_analysis_20260820.md) | [工作簿](hrbrain/hrbrain_cli_param_hallucination_analysis_20260820.xlsx) | [候选](hrbrain/param_concepts.json) | `ebae9af7df01928031bf444e829e10232e97b058d5b114774a416445adacf55c` |
## 跨候选合并审计
12 份候选均从同一冻结正式表独立派生。与冻结基线比较:
- 共涉及 39 个唯一 concept,其中 20 个新增、19 个既有 concept 精确扩围;
- 共新增或修改 63 个精确 command override;
- 共新增 309 个验证 fixture;
- 没有删除任何冻结 concept、override 或 fixture;
- `$schema`、version、morphological rules 等非目标结构全部保持不变;
- 所有 override 都属于对应目标产品;
- 跨产品不存在同名 command override 冲突,也不存在重复新增 fixture。
有 6 个 concept 被多个产品共同扩展:
| 共享 concept | 产品 | 合并结论 |
|---|---|---|
| `pagination_size` | report、hrbrain | 非命令字段相同,合并 commands 并集 |
| `content_text` | ding、markdown | 非命令字段相同,合并 commands 并集 |
| `open_conversation_id` | ding、event | 非命令字段相同,合并 commands 并集 |
| `search_query` | event、devdoc、hrbrain | 非命令字段相同,合并 commands 并集 |
| `local_output_path` | markdown、audit | 非命令字段相同,合并 commands 并集 |
| `page_number` | devdoc、hrbrain | 需要显式并集:保留 Devdoc 新成员 `page-number`,同时加入 HRBrain 精确 commands |
前五项没有语义冲突;`page_number` 也不是业务矛盾,但不能让后写入的独立候选覆盖前一个候选的
成员或命令范围,必须由评审者明确构造并集。
## 跨产品共性结论
### 可以由当前参数字典安全处理
- 同一实体、同一角色、同一值域、同一单复数、同一单位,且值可原样传递的 flag 拼写;
- 只在精确命令内成立的 command-level alias;
- 宽泛真实参数在精确命令中的 concept 绑定;
- 值域、角色、单复数、分页模型、输入/输出或产品边界明确错误时的 block;
- 同一输入存在多个合理 canonical 目标时的 ambiguous;
- 无业务参数、位置参数或敏感输出型命令的纯 fail-closed 保护。
### 当前参数字典不能解决
- 位置参数与 flag 之间的 argv 角色转换;
- userId、openDingTalkId、工号、手机号、会话 ID、消息 ID、人才池编码等值域查询或互转;
- 单值与多值拆分/合并,CSV 与 JSON 互转,正文包装为结构化字段;
- 日期补时区、duration/TTL/timeout 单位换算、offset/cursor/page 模型转换;
- JSON 数组成员或表达式的深层业务校验;
- 同名真实 flag 在业务层与全局层承担不同角色时的自动意图消歧;
- 新增真实命令、flag、权限、搜索、连接配置、干运行能力或安全确认;
- 修复 Help/Schema/Skill/Runtime 的事实漂移。
这些事项应落在 leaf Contract/Runtime、CLI 命名与校验、Skill/Schema 来源或显式跨产品编排,
不能通过扩大 alias 范围掩盖。
## 建议落地顺序
1. 先单独落地并回归 Devdoc、Live、MCP 三个全门禁通过候选。
2. 为 Report、Ding、Event、Markdown、AI Search、Audit、PAT、HRBrain 补齐对应 complete-command
模板;HRBrain 已在隔离副本证明补齐后全量应用通过。
3. 先修 Whiteboard 在 `NewSchemaSourceRootCommand()` fresh declaration-only 树中的可见性,证明
原样 `go generate ./internal/cli` 可运行,再加入 2 条模板。
4. 同步处理明确的配套契约项:Ding Shortcut Identity、Markdown Skill diff、PAT dry-run 写入、
Report 发送人值域;Devdoc Help/Skill 漂移可作为非阻断后续但不应遗忘。
5. 从**冻结正式表或选定的新正式基线**重建一个合并候选,按概念/override/fixture 语义合并,
不要依次复制 12 个完整 JSON 文件。
6. 对 `page_number` 显式保留 Devdoc 的 `page-number` member 与 HRBrain commands 并集,对另外 5 个
共享 concept 合并命令范围。
7. 统一执行 `go generate ./internal/cli`、全部 fixture/PreParse、complete-command canonical payload、
block/ambiguous dispatch 前保护、非目标回归、`internal/cli`、`internal/pipeline`、产品专项、
完整 `internal/app`、generated drift、Schema Catalog 与 Runtime confirmation truth。
8. 只有合并候选在新的同一提交上全绿后,才替换正式 `internal/cli/param_concepts.json`;本轮各
产品候选仍保持“评审草稿”身份。
## 最终落地判断
本轮分析和候选设计已经完成,12 个产品的参数事实、可安全兜底范围、当前能力边界与落地依赖均
已形成可审计交付。可以立即进入正式评审的是 Devdoc、Live、MCP;其余候选不是规则逻辑未知,
而是明确受 complete-command 模板、Whiteboard 生成树可见性或已识别 Runtime/Skill 契约缺口约束。
当前不建议把任意一份候选直接覆盖正式表,也不建议把 12 个完整候选按顺序复制。正确落地形态是:
先修前置、按冻结差异合并、显式处理共享 concept、补 45 个模板,再在一个隔离合并副本中统一跑完
全门禁。
@@ -0,0 +1,178 @@
# DWS Dev CLI 参数幻觉分析
## 1. 结论摘要
本报告以线上 `main` 提交 `fd24619437afcb92638d6a71e0bfd9254815fe06`(2026-08-11 14:08:11 +0800)为冻结基线,只使用该提交的真实 Cobra 命令树与 Help、运行时组装 Schema、Dev Skill、`schema_command_exclusions.go`、命令实现和正式 `internal/cli/param_concepts.json`。没有使用历史 badcase、`dws-eval`、`merged_scan.json`、历史工作簿、固定 Catalog、用户 Shortcut 或插件。
Dev 的主要风险不是单个 flag 拼写,而是同一产品内存在多层、近似但不可互换的标识符和配置角色。应用既可由 `unifiedAppId` 标识,也可能出现 `appKey`;机器人连接使用独立的 `robotClientId/secret`;版本同时有 `version-id` 与创建时的 `version`;机器人提交还有异步 `task-id`。与此同时,应用名、机器人名、Agent 名和删除确认名都带 `name`,普通文本、国际化 JSON、图标媒体、预览媒体及六类 URL/安全端点又大量共享相似词根。模型若统一写成 `--id`、`--client-id`、`--name`、`--description`、`--media-id` 或 `--url`,无法仅凭名称安全决定目标。
量化结果如下:
- 官方 Cobra 树共有 49 个 runnable 路径,全部可见;其中 38 个路径带业务参数,合计出现 140 次 canonical 业务参数,使用 77 个不同 canonical flag 名。
- 运行时 Schema 发布 34 个 Dev 工具;另有 12 个可执行父命令,以及 3 个被 `schema_command_exclusions.go` 精确排除的公开 runnable leaf:`dev app version check-approval`、`dev connect list`、`dev connect restart`。
- 34 个 Schema leaf 合计发布 108 次参数、52 个不同 flag 名;逐 leaf 对账真实 Help,公开业务参数差异为 0。
- 官方树还有 6 次原生 hidden flag,涉及 4 个名称:`keyword`、`member-user-ids`、`daemon-supervise`、`daemon-worker`。
- Dev Skill 的机械扫描报告 9 条“未知命令”,逐项复核后均来自可执行 `dev connect` 父命令或上述 exclusion 命令,真实 Skill 参数漂移为 0。
- 聚合得到 7 类参数问题、81 条“问题—命令”明细,覆盖全部 38 个参数化官方路径。
- 正式基线只为 Dev 生成 1 个命令条目、2 个 alias、2 个 block、0 个 ambiguous;候选草稿扩展到 36 个受 reducer 管理的参数化 leaf、246 个 alias、253 个 block、26 个 ambiguous。
- 候选相对正式文件新增 31 个 Dev concept,扩展 `app_id`、`page_cursor`、`page_number`、`pagination_size`、`search_query`、`user_ids` 6 个既有 concept 的 Dev scope,并新增 20 个 Dev command override;既有 override、morphological rules 和 253 条 fixture 均未变化。
- 候选已通过 22 组 alias/canonical 最终 payload 或本地结果等价、13 组 block/ambiguous、2 组原生隐藏兼容、4 组非 Dev 回归,以及 PreParse、三包测试、generated drift 和 Schema Catalog policy。
第一轮可以安全落地“同实体、同角色、同 cardinality、值可原样传递”的参数名归一,并对 `id/client-id/name/media-id/url` 等无唯一答案的输入进行 block 或 ambiguous。不能通过别名表解决的内容包括:标识符查询与互换、名称查 ID、单值/列表转换、i18n JSON/URL 列表构造、`dev connect` 可执行父命令的 reducer 边界、Schema exclusion 以及 `dev doc search` 后端网关不可用。
## 2. 七类参数问题
### 2.1 应用、凭证、机器人、版本和任务标识符容易被统一写成 `id/client-id`
Dev 至少存在以下相邻标识符:
- `--unified-app-id`:应用统一 ID,出现在 29 个 Schema leaf,并被 local/excluded leaf 继续使用;
- `--app-key`:应用 key,仅用于应用查询和列表过滤;
- `--robot-client-id` / `--robot-client-secret`:机器人连接凭证;
- `--version-id`:已创建版本的 ID;
- `--version`:创建版本时的版本号或版本标签;
- `--task-id`:机器人提交后的异步任务 ID;
- `--app-group-id`:应用列表过滤维度。
正式表的 `app_id` 只覆盖 `dev app get`,因此大部分 `--app-id/--application-id` 仍无法在其他 Dev leaf 中兜底。候选将 `app_id` 扩展到所有受治理的 `--unified-app-id` leaf,但明确排除 `dev connect` 可执行父命令;另外分别建立 appKey、robot client、version ID、version label 与 robot task ID concept。
`--client-id` 不能被无条件改成 `--robot-client-id`:它已经是 root persistent 真实 flag,语义属于 DWS 全局客户端配置。中央规则不会覆盖真实 flag,候选只允许 `bot-client-id/robot-app-client-id` 等角色明确的名称。多 ID leaf 中的 `--id` 返回 ambiguous,不做值域猜测。
### 2.2 应用名、机器人名、Agent 名与删除确认名共享 `name` 词根
应用 create/update/list 中的 `--name` 表示应用名称;robot config 中的 `--name` 表示机器人展示名称;robot submit 同时有 `--name` 与 `--robot-name`;`dev connect` 还有 Agent 运行配置中的命名语义。它们不能只因为真实 flag 叫 `name` 就归为同一个全局 concept。
最敏感的是 `dev app delete --confirm-name`。这个参数是删除确认值,不是普通应用名入口。候选仅接受 `--confirm-app-name/--confirmation-name` 等明确确认型名称,并 block `--name/--app-name`,防止普通文本被自动提升为删除授权。
### 2.3 成员、审批人、权限范围和事件订阅混合单值/列表及不同角色
代表性参数包括:
- 成员列表:`--user-ids`,原生隐藏兼容为 `--member-user-ids`;
- 成员类型:`--member-type`;
- 版本审批人单值:`--approver-user-id`;
- 权限写入列表:`--scope-values`;
- 权限查询过滤:`--scope-value` 与 `--scope-type`;
- 事件订阅列表:`--event-codes`;
- connect 运行范围:`--allowed-users/--allowed-groups/--owner-user-id/--notify-staff-id`。
这些值可能都表现为 ID 或 CSV,但业务角色和 cardinality 不同。候选允许同角色、同单复数的名称变体,禁止把单个 `user-id` 自动扩展成 `user-ids`,也不允许把权限查询过滤 `scope-value` 改成权限写入列表 `scope-values`。
### 2.4 搜索、过滤、分页、排序、状态和本地输出协议名称不统一
应用、事件、权限、版本列表使用 `cursor + page-size`,而 `dev doc search` 使用 `page + size + query`。应用列表又同时出现 `name/robot-name/creator/develop-type/filter-cool-app/sort-type/sort-order`;权限列表同时出现 `scope-type/scope-value/api-status/auth-status/keyword`。
候选在同一协议内复用 `page_cursor`、`page_number`、`pagination_size` 和 `search_query`,并在相反协议上保护。它不会计算 page 与 cursor,也不会翻译 sort/status/type 的枚举值。
`dev connect list/status --json` 是本地 bool 输出开关,不等同于全局 `--format json` 字符串协议。二者不能由单纯 alias 安全互换,保留为当前边界。
### 2.5 描述、简介、国际化 JSON、媒体 ID 和 Agent 配置文本容易被当作通用 `text/payload`
应用与机器人命令同时使用:
- 普通文本:`--desc`、`--brief`;
- 国际化 JSON:`--i18n-name`、`--i18n-brief`、`--i18n-description`;
- 媒体资源:`--icon-media-id`、`--preview-media-id`;
- 能力列表:`--skills`;
- connect 配置:model、memory、workdir、knowledge、card template、audit sheet 等多个独立角色。
候选只对 `description/app-description/robot-description`、`icon-id/preview-id` 等角色明确且值可原样传递的名称进行改写。在 robot submit 同时存在图标和预览媒体时,泛化 `--media-id` 返回 ambiguous;`data/payload/text` 不会被猜成 i18n JSON 或普通描述。
### 2.6 回调、出口、Web 入口、重定向、SSO 和 IP 白名单属于不同安全端点
三组命令暴露了不同端点角色:
- robot config:`--event-callback-url`、`--outgoing-url`;
- webapp config:`--homepage-url`、`--omp-url`、`--pc-homepage-url`;
- security config:`--redirect-urls`、`--sso-urls`、`--ip-whitelist`。
这些参数不能统一成 `url/urls`。候选为每个角色建立独立 concept,并把泛化 `url/urls/callback-url` 设为 ambiguous 或 block。别名层只改参数名,不拆分逗号列表、不做 URL/IP 格式校验,也不推断安全策略。
### 2.7 可执行父命令、Schema exclusion、隐藏兼容 flag 与运行时可用性形成边界
同一提交内存在四个不同层次:
- 49 个官方 runnable Cobra 路径;
- 34 个 Agent Schema 工具;
- 36 个候选中央 reducer 命令条目;
- 最终接口或本地运行是否可用。
`dev connect` 本身是可执行父命令,并直接承载 27 个公开业务 flag,但当前中央 reducer 不治理这个父命令。`dev connect list/restart` 与 `dev app version check-approval` 可执行,却被 Schema 精确排除。`dev doc search` 的命令、Schema 和参数可以全部正确,但 Skill 明确记录当前运行时网关不可用,应使用 `devdoc article search` 等替代路径。
候选没有通过 JSON 改写这些独立边界:只治理 36 个已经进入 reducer 的参数化 leaf;`dev connect` 父命令和 `connect list` 保持 canonical 行为;Schema exclusion 与后端故障分别列为当前无法解决。
## 3. 当前别名表可以直接实施的方案
候选完整文件位于同目录 `param_concepts.json`,主要改动如下:
1. 新增 31 个 Dev 专用 concept,覆盖 appKey、名称、机器人凭证、版本/任务、人员角色、权限范围、事件、文本/i18n、媒体、URL/安全端点等。
2. 扩展 `app_id`、`page_cursor`、`page_number`、`pagination_size`、`search_query`、`user_ids` 6 个既有 concept 的 Dev 精确命令范围。
3. 新增 20 个 command override,用于绑定真实 `name/desc/version` 等宽泛 canonical、限定 scoped alias,并配置 `scope_strict`、block 和 ambiguous。
4. 候选生成 36 个 Dev 命令条目;没有把 `dev connect` 父命令或 `dev connect list` 伪装成已治理对象。
生成影响面由正式基线的 1/2/2/0 扩展为 36/246/253/26(命令/alias/block/ambiguous)。独立审核确认:
- 36 个生成路径全部存在于冻结提交的官方树;
- 246 个 alias 的目标全部是对应 leaf 的真实 canonical flag;
- alias 来源均未与该 leaf 的真实 flag 冲突;
- 253 个 block 和 26 个 ambiguous 均未拦截真实 canonical flag;
- `dev connect` 可执行父命令没有进入生成结果;
- 既有非 Dev override、morphological rules 和 253 条 fixture 完全不变。
规则数量较大主要来自 `unified-app-id` 覆盖面、同一 leaf 内多角色 ID/名称/URL 的保护性展开,以及列表协议的正反向 block。数量本身不是通过依据,完整 generated audit 与最终 payload 等价才是准入依据。
## 4. 当前能力支持不了或不应该自动处理的事项
以下事项不能通过 `param_concepts.json` 解决:
- unifiedAppId、appKey、robot clientId、versionId、taskId 之间的查询或转换;
- 把 root `--client-id` 自动解释成 `--robot-client-id`;
- 应用名、机器人名或 Agent 名到稳定 ID 的解析;
- 单个 userId 与 user-ids/allowed-users 之间的自动扩展或收缩;
- `scope-value` 查询过滤与 `scope-values` 写入列表之间的转换;
- 把普通文本构造成 i18n JSON,或自动拆分 URL/IP 列表;
- 把本地 bool `--json` 与全局字符串 `--format json` 自动互换;
- 让 `dev connect` 可执行父命令的 27 个业务 flag 自动进入中央 reducer;
- 让 3 个 Schema exclusion 命令通过别名表进入 Agent Schema;
- 修复 `dev doc search` 后端网关不可用。
这些边界不阻塞第一轮安全的参数名治理,但必须在 Help/Skill 中持续明确,不能把“PreParse 能改名”写成“最终业务能力已可用”。
## 5. 候选草稿验证结果
候选仅在冻结快照 `/private/tmp/dws-main-param-analysis.HcTfUP` 中临时替换正式输入并重新生成、构建和测试;当前工作区正式 `internal/cli/param_concepts.json` 与 `internal/cli/param_aliases_generated.go` 的 SHA-256 分别保持 `1ba7dc90…6fed` 和 `4e4bbc41…4f36`,均未修改。
验证结果:
- JSON 结构、候选 scope 和 generated 全量规则审核通过;
- `go generate ./internal/cli` 结果稳定,生成 316 个全局命令条目;
- 22 组 alias/canonical 最终 dry-run payload 或隔离 HOME 本地结果完全等价;
- 13 组 block/ambiguous 均在 dispatch 前停止;
- 2 组原生 hidden 兼容 `keyword/member-user-ids` 保持正常;
- 4 组 Calendar、Doc、Drive、Mail 非目标 alias 最终 payload 未变化;
- 3 组生产入口 PreParse 验证通过:`stop --app-id`、`restart --bot-client-id` 和 excluded `version check-approval` 均命中候选;
- `dev connect --app-id` 仍返回 unknown flag,证明可执行父命令没有被候选误标为中央治理成功;
- `internal/cli`(128.774s)、`internal/pipeline`(0.967s)、`internal/app`(240.818s)全部通过;
- `check-generated-drift.sh` 通过,Schema assembly 两次结果一致;
- `check-schema-catalog.sh` 通过,最终仍为 27 个产品、1018 个工具;
- runtime confirmation truth、flag/help/schema homology 和 Catalog assembly determinism 均通过。
## 6. 第一轮改造建议
1. 先评审并落地 31 个 Dev concept、6 个既有 concept 的 Dev scope 和 20 个精确 override,不修改真实 CLI flag。
2. Dev owner 重点审核 `dev app list`、`permission list`、`robot config/submit`、`security config`、`webapp config` 六个多角色 leaf 的 block/ambiguous。
3. 单独评审 `dev connect` 父命令是否应进入中央 reducer;在没有框架级支持前,只声明 canonical 边界。
4. 单独评审 3 个 `schema_command_exclusions.go` 项是否仍需排除,这与参数 alias 治理分开提交。
5. 将本报告的 22/13/2/4 行为集转为长期测试,并保留 `--client-id`、`confirm-name`、`media-id`、`url/urls` 四类强保护回归。
## 7. 可复用到其他产品的流程
1. 冻结线上 main commit,并在隔离副本重建二进制;
2. 合并官方 runnable Cobra 树、runtime Schema、exclusion、可执行父命令和隐藏 flag 形成全量清单;
3. 逐 leaf 对账真实 Help、完整 Schema 和 Skill;
4. 按业务实体、角色、cardinality、值域、数据结构和安全含义聚合参数问题;
5. 只有值可原样传递且目标唯一时配置 alias,否则使用 block/ambiguous 或列为不支持;
6. 基于冻结提交正式 `param_concepts.json` 生成完整候选,不覆盖当前工作区正式文件;
7. 全量审核命令路径、alias 目标、真实 flag 冲突、保护规则和非目标 diff;
8. 在隔离副本验证生成确定性、PreParse、最终 payload、本地结果、保护、原生兼容、非目标回归、包测试和政策门禁;
9. 报告同时交付可落地规则与真实运行边界,不能用 Schema 存在、Help 可解析或 PreParse 命中替代最终行为证据。
@@ -0,0 +1,8 @@
# Dev CLI 参数幻觉补充复核(2026-08-21)
独立候选基于线上 `main` `11934eed057267d97e7442ddd420c711ee1802dc`:
71 concepts / 370 overrides / 749 fixtures,生成与嵌入式 PreParse 通过。
36 个命令产生 39 alias、161 block、26 ambiguous。Dev/DevApp 共用 app name、version ID、icon media ID、
member type 等真正同域概念;`member-role` 只保留为成员命令局部别名。机器人 clientId、taskId、appKey
保持不同标识符命名空间,不把无角色 `id/key` 自动提升。
File diff suppressed because one or more lines are too long
@@ -0,0 +1,166 @@
# DWS DevApp CLI 参数幻觉分析
## 1. 结论摘要
本报告以线上 `main` 提交 `fd24619437afcb92638d6a71e0bfd9254815fe06`(2026-08-11 14:08:11 +0800)为冻结基线,只使用同提交的官方 Cobra 命令树与 Help、运行时组装 Schema、DevApp Skill 及子文档、Shortcut 声明/实现、参数 alias 生成器和正式 `internal/cli/param_concepts.json`。没有使用历史 badcase、`dws-eval`、`merged_scan.json`、历史工作簿、固定 Catalog、用户 Shortcut 或插件。
DevApp 不是 `dev app` 的简单命令别名,而是一套独立的 helper-only Shortcut 产品面。它与 `dev app` 操作相同的开放平台应用领域,但命令路径、可见性和生成范围不同:官方树有 30 个参数化 `devapp +...` Shortcut,只有 19 个公开并进入 Runtime Schema,另有 11 个隐藏兼容命令。参数 alias 生成器会硬失败拒绝这 11 个隐藏路径,因此“命令可执行”不等于“可由当前中央别名表治理”。
量化结果如下:
- 官方 Cobra 树共有 30 个 runnable 路径,全部带业务参数;19 个公开、11 个隐藏,合计出现 90 次 canonical 参数,使用 41 个不同 flag 名。
- Runtime Schema 发布 19 个 DevApp 工具,合计 57 次参数、26 个不同 flag 名;逐 leaf 对账真实 Help,公开参数差异为 0。
- 11 个隐藏 Shortcut 是:`credentials-get`、`event-subscribe/unsubscribe`、`permission-add/remove`、`robot-config/enable/disable`、`security-config`、`version-create/publish`。
- 对实际 Skill 主文件及 `dev/` 子目录 13 个文档进行专门扫描,命中 21 行 `dws devapp` 命令文本;唯一机械告警是说明占位符 `dws devapp <shortcut> --help`,真实命令/参数 drift 为 0。
- 聚合得到 6 类参数问题、66 条“问题—命令”明细,覆盖全部 30 个参数化官方路径。
- 正式基线对 DevApp 的生成覆盖为 0 个命令、0 alias、0 block、0 ambiguous;候选草稿覆盖 19 个公开 Shortcut,生成 134 个 alias、122 个 block、10 个 ambiguous。
- 候选相对正式文件新增 12 个 DevApp concept,扩展 `app_id`、`page_cursor`、`pagination_size`、`search_query`、`user_ids` 5 个既有 concept 的 DevApp scope,并新增 7 个 command override;既有 override、morphological rules 和 253 条 fixture 均未变化。
- 候选已通过 17 组 alias/canonical 最终 ToolCaller 参数等价、10 组 block/ambiguous、11 个隐藏 Shortcut 边界、4 组非 DevApp 回归、三包测试和两项政策门禁。
第一轮可安全治理 19 个公开 Shortcut:统一应用 ID、appKey/版本 ID、应用与机器人名称、应用描述/图标、成员列表与角色、权限查询过滤、游标分页、搜索、应用列表筛选/排序和 WebApp 三类 URL。11 个隐藏命令的参数问题虽然已经分析,但不能写入当前候选,否则生成器会拒绝整个表;应继续使用 canonical 参数,或先独立扩展 reviewed command root。
## 2. 六类参数问题
### 2.1 unifiedAppId、appKey、版本 ID 和分组 ID 容易被统一写成 `id/client-id`
DevApp 的 ID 体系包括:
- `--unified-app-id`:应用全树主键,出现在 28 个 Shortcut;
- `--app-key`:应用列表的 appKey/clientId 过滤;
- `--app-group-id`:应用分组过滤;
- `--version-id`:已创建版本的 ID。
Skill 明确 `appKey = clientId`,但二者都不等于 `unifiedAppId`。写操作必须由用户或上游结果提供明确的 `unifiedAppId`,不能根据 appKey/clientId 或应用名自动反查并继续写。
候选将 `app_id` 扩展到 17 个公开单应用 Shortcut,并为公开 `+list` 的 appKey、三个公开版本 leaf 的 versionId 建立独立 concept。`+list` 同时存在多个 ID/filter 角色,泛化 `--id` 返回 ambiguous;appKey、versionId、groupId 与 unifiedAppId 之间互相 block。
### 2.2 应用名、机器人名、创建人、描述、简介和图标共享宽泛文本/媒体词根
公开命令中:
- `+create/+update` 的 `--name` 表示应用名;
- `+list` 同时有应用 `--name`、`--robot-name` 和 `--creator`;
- `+create/+update` 使用 `--desc` 与 `--icon-media-id`。
隐藏 `+robot-config` 还使用机器人 `name/brief/desc/icon/skills`,隐藏 `+version-create` 使用版本说明 `desc`。因此 `name/description/text/media-id` 不是跨产品面全局同义参数。
候选只在 19 个公开 Shortcut 内建立 app name、robot name、description、icon concept;`+list --query` 无法判断应用名、机器人名或创建人,返回 ambiguous;`--media-id` 不会被猜成图标媒体 ID。
### 2.3 成员、权限、事件和版本审批参数混合角色、单复数与安全含义
代表性参数包括:
- 公开成员写入:`--user-ids`、`--member-type`;
- 公开权限查询:`--scope-value`、`--scope-type`、`--api-status`、`--auth-status`;
- 隐藏权限写入:`--scope-values`;
- 隐藏事件订阅:`--event-codes`;
- 隐藏版本发布:`--approver-user-id`、`--confirmed-sensitive`。
它们不能因为都是 ID、列表或状态就合并。候选对公开成员 leaf 复用 `user_ids` 并新增 member type concept,明确 block 单值 `user-id`;对公开 `permission-list` 分离 scope filter/type 与两种 status,泛化 `scope/status/type` 返回 ambiguous。隐藏权限、事件和发布命令不进入候选。
### 2.4 游标分页、搜索、筛选、排序和版本标签协议名称不统一
`+list/+event-list/+permission-list/+version-list` 使用 `cursor + page-size`;event/permission 搜索使用 `keyword`;应用列表还有 group、creator、develop-type、filter-cool-app、sort-order、sort-type。隐藏 `+version-create --version` 则是人工填写的版本标签,不是服务端 `version-id`。
候选在同协议内扩展 `page_cursor`、`pagination_size` 和 `search_query`,并为 `+list` 增加角色明确的 scoped alias。它不把 page 换算成 cursor,不翻译枚举值,也不把 version 标签和 versionId 互换。
### 2.5 机器人回调、Web 首页、安全 URL/IP 与模式参数属于不同端点角色
公开 `+webapp-config` 同时具有:
- `--homepage-url`:移动/H5 首页;
- `--pc-homepage-url`:PC 首页;
- `--omp-url`:OMP 地址;
- `--h5-page-type`:页面类型。
隐藏 `+robot-config` 还有 event/outgoing URL、mode、SSL;隐藏 `+security-config` 还有 redirect URL、SSO URL 与 IP whitelist。候选只治理公开 WebApp 三类 URL,并把 generic `url/home-url` 设为 ambiguous。URL/IP 列表、协议校验和值转换不属于参数别名层。
### 2.6 19 个公开与 11 个隐藏 Shortcut 的生成、Schema 和安全生命周期边界
本次最重要的边界是:
```text
官方 Cobra 可执行:30
├─ 公开并进入 Runtime Schema:19
│ └─ 当前 alias generator 可治理:19
└─ 隐藏兼容:11
└─ 当前 alias generator 硬失败拒绝:11
```
最初将 11 个隐藏命令加入候选时,`go generate ./internal/cli` 明确报告每个 path “does not match any runnable Cobra leaf/command”。候选因此主动收敛到 19 个公开 Shortcut。这不是漏做,而是当前生成入口与声明树的真实能力边界。
此外,参数名正确也不等于业务完成:删除、停用、成员移除仍受 Runtime confirmation;版本发布的审批人必须用户选择;permission/robot/webapp 配置后仍需版本进入 `RELEASE/AUDIT/UNDER_REVIEW` 才能判断上线状态。参数别名不拥有这些门禁。
## 3. 当前别名表可以直接实施的方案
候选完整文件位于同目录 `param_concepts.json`,主要改动如下:
1. 新增 12 个 DevApp concept:appKey、应用/机器人名称、description、icon、member type、permission scope filter/type、version ID、三类 WebApp URL。
2. 扩展 `app_id`、`page_cursor`、`pagination_size`、`search_query`、`user_ids` 5 个既有 concept 的 DevApp 精确命令范围。
3. 新增 7 个公开 command override:`+create/+update/+list/+member-add/+member-remove/+permission-list/+webapp-config`。
4. 19 个公开 Shortcut 全部进入生成结果;11 个隐藏 Shortcut 不进入候选。
生成影响面由正式基线的 0/0/0/0 扩展为 19/134/122/10(命令/alias/block/ambiguous)。独立审核确认:
- 19 个生成 path 全部是冻结提交中公开、可执行、进入 Schema 的 Shortcut;
- 134 个 alias 的目标全部是对应 leaf 的真实 canonical flag;
- alias 来源没有与该 leaf 的真实 flag 冲突;
- 122 个 block 和 10 个 ambiguous 没有拦截真实 flag;
- 11 个隐藏 path 全部不在 generated table;
- 既有非 DevApp override、morphological rules 和 253 条 fixture 完全不变。
正式合入时还应与 Dev 产品候选做一次跨产品 concept 合并评审。两套命令面参数高度相似,长期更适合让共享 `app_id/pagination/user_ids` concept 同时列出 `dev app` 和 `devapp +` path,并对角色相同的新 concept 去重,而不是保留两套互相独立的同义词定义。本报告的候选必须单独可用,因此仅基于正式基线生成,没有依赖尚未合入的 Dev 草稿。
## 4. 当前能力支持不了或不应该自动处理的事项
以下事项不能通过本次 `param_concepts.json` 候选解决:
- 为 11 个隐藏 Shortcut 配置中央 alias;
- unifiedAppId 与 appKey/clientId 自动互转;
- 应用名或机器人名自动解析成稳定应用 ID;
- 单个 userId 与 user-ids 自动扩展或收缩;
- `scope-value` 查询过滤与 `scope-values` 写入列表互换;
- 翻译 develop/filter/status/type/mode 等枚举值;
- 拆分或合并 URL、IP、skills、event-codes 等列表;
- 主动读取、展示或放宽 appSecret/clientSecret 脱敏;
- 用 alias 代替 `--yes`、审批人选择或 `confirmed-sensitive`;
- 用参数名归一保证配置已经上线或版本已经 `RELEASE`。
其中第一项是当前框架的硬能力缺口,其余是值转换、实体解析、安全或业务生命周期问题。它们不阻塞 19 个公开 Shortcut 的第一轮安全治理。
## 5. 候选草稿验证结果
候选仅在冻结快照 `/private/tmp/dws-main-param-analysis.HcTfUP` 中临时替换正式输入并重新生成、构建和测试;当前工作区正式 `internal/cli/param_concepts.json` 与 `internal/cli/param_aliases_generated.go` 未修改。
验证结果:
- JSON 结构、候选 scope 和 generated 全量规则审核通过;
- `go generate ./internal/cli` 稳定生成 300 个全局命令条目,其中 DevApp 19 个;
- 17 组 alias/canonical 通过注入 Runner 的最终 ToolCaller 调用序列与参数等价;读类 Shortcut 未使用“同样鉴权失败”作为等价证据;
- 10 组 block/ambiguous 在 PreParse 中命中预期保护;
- 11 个隐藏 Shortcut 全部确认可执行且不在 generated table;`+credentials-get --app-id` 保持 unknown flag,而 canonical `--unified-app-id` 仍可解析;
- 4 组 Calendar、Doc、Drive、Mail 非目标 alias 最终 payload 未变化;
- `internal/cli`(127.945s)、`internal/pipeline`(1.312s)、`internal/app`(201.296s)全部通过;
- `check-generated-drift.sh` 通过,参数 alias 与 Schema assembly 两次结果一致;
- `check-schema-catalog.sh` 通过,最终仍为 27 个产品、1018 个工具;
- runtime confirmation truth、flag/help/schema homology 和 Catalog assembly determinism 均通过。
## 6. 第一轮改造建议
1. 先落地 12 个 DevApp concept、5 个既有 concept 的精确 scope 和 7 个公开 override,不修改真实 CLI flag。
2. DevApp owner 重点审核 `+list`、`+permission-list`、`+member-add/remove`、`+webapp-config` 的 block/ambiguous。
3. 保持 11 个隐藏 Shortcut canonical;若确需参数治理,先独立设计 alias generator 对隐藏 reviewed command 的收录契约与安全测试。
4. 与 Dev 候选合并前做 concept 去重,确保相同实体/角色使用共享 concept,路径仍精确列举。
5. 将 17/10/11/4 行为集转为长期仓库测试,尤其固定“隐藏命令 alias 仍不命中”的能力边界。
## 7. 可复用到其他产品的流程
1. 冻结线上 main,并在隔离副本重建二进制;
2. 同时遍历公开和隐藏 runnable Cobra 路径,不能只看 Runtime Schema;
3. 对账公开 Help/Schema,针对实际 Skill 目录补充扫描,不把“文件未发现”当作无漂移;
4. 按实体、角色、cardinality、值域、结构和安全语义聚合问题;
5. 基于正式别名表生成完整候选,并实际运行 generator 验证命令是否属于 reviewed root;
6. generator 硬失败的 path 列为能力边界,不能通过删除检查或伪造 path 绕过;
7. 对读类 Shortcut 使用注入 Runner 验证最终参数,不能拿相同鉴权错误当等价;
8. 验证保护、隐藏边界、非目标回归、包测试、生成确定性和 Schema policy;
9. 在跨产品命令面高度同构时,正式合并前做 concept 去重与共享,而每个产品候选仍应可独立复现。
@@ -0,0 +1,8 @@
# DevApp CLI 参数幻觉补充复核(2026-08-21)
独立候选基于线上 `main` `11934eed057267d97e7442ddd420c711ee1802dc`:
66 concepts / 356 overrides / 716 fixtures,生成与嵌入式 PreParse 通过。
19 个命令产生 22 alias、101 block、10 ambiguous。与 Dev 的同域实体已统一,删除重复的
`devapp_version_id/devapp_app_name/devapp_icon_media_id/devapp_member_type` 中央概念;命令局部角色仍用
scoped alias,避免跨产品扩散。
File diff suppressed because one or more lines are too long
@@ -0,0 +1,158 @@
# Devdoc 产品 CLI 参数幻觉分析
## 结论摘要
本分析以线上 `origin/main` 冻结提交
`aa4ae9a90323aa97e5cebdb5045b129f0f14e0df` 为唯一基线,使用该提交重新构建的
`dws`、运行时 Schema、官方 Cobra 树、实现代码、dingtalk-misc Devdoc Skill 与冻结正式
`internal/cli/param_concepts.json`。未使用固定 Catalog、历史 badcase、用户 Shortcut 或已安装
插件,也没有修改当前工作区正式别名表。
Devdoc 产品有 1 个 Agent 可见叶 `devdoc article search`,另有 1 个隐藏 hint-only 兼容节点
`devdoc search`,后者只报错并指向正式路径,不执行业务检索。相同 MCP 接口还被 `dev doc
search` 暴露为 Dev 产品叶,本轮不跨产品扩大规则。
主要风险是检索词三入口被误认为三个不同参数、页码/每页数量与 cursor/offset 混用、非法数字
被 Runtime 静默回退、对象 ID 或错误诊断字段被误当可独立搜索参数,以及 Help/Skill 的默认
输出和必填描述与真实行为不完全一致。候选扩展既有 `search_query` 与 `page_number` concept,
新增 1 个精确 override 和 17 个验证 fixture;所有自动映射均保持字符串或数字文本原值,不做
拼接、查询、数值修复或跨命令路由。
候选已通过生成器、PreParse、9 组 alias/canonical payload 比较、9 组 block/ambiguous、
非法数值与双入口行为验证、非目标结构恒等、`internal/cli`、`internal/pipeline`、generated
drift、Schema Catalog 政策与完整 `internal/app`(295.860 秒)。别名候选可以进入落地评审;
Help/Skill 漂移和 Runtime 数值/双入口行为仍需独立修复,不能把产品整体标为完全解决。
## 参数问题
### 1. 检索词存在位置参数、公开 flag 和隐藏原生兼容 flag 三个入口
真实命令接受位置 `[keyword]`、公开 `--query` 和隐藏 `--keyword`。Schema 正确发布位置
`keyword` 与参数 `query` 的 require-one-of;Skill 只展示 `--query`,容易让 Agent 把 `q`、
`keywords`、`search-word`、`search-term` 或 `query-text` 当成不存在的新字段。
候选把该命令加入既有 `search_query` concept,并在精确 override 中补
`search-term/query-text → query`。隐藏 `--keyword` 保持原生,不重复重写;位置参数也保持
原生,因为 PreParse 不能把 flag 值搬成 argv。若位置值和 `--query` 同时提供,Runtime 静默
采用 flag 值,参数表不能改成冲突错误。
### 2. 一基页码、每页数量、offset 和 cursor 属于不同分页模型
该接口只有一基 `--page` 和每页上限 `--size`。正式表已将 `limit/page-size/max-results/...`
归一到 size,并将 `page-no/current-page/page-num` 归一到 page;但常见 `page-number` 和
`results-per-page` 尚未覆盖。cursor、page-token、offset、page-index 则不是同一模型。
候选把 `page-number` 加入只作用于本命令的 `page_number` concept,并增加
`results-per-page → size`。cursor/offset/zero-based index 明确 block,不把值改写为页码。
### 3. CLI 接受字符串,Runtime 才转数字且非法值静默回退
Cobra Help/完整 Schema 的 CLI type 是 string,Contract 同时声明 MCP interface type 为
number。实现用 `strconv.Atoi`,`page < 1` 或解析失败回退 1,`size < 1` 或解析失败回退 10;
因此 `--page abc --size 0` 不会报参数错误,而会悄悄执行第一页、每页十条。
别名表只能改 flag 名,不能验证、转换或限制值。正式修复应把 page/size 声明为正整数或在
RunE 显式拒绝非法输入,并同步 Help/Schema。候选仅记录该边界,不伪造数值治理。
### 4. 开放平台文章检索不是对象读取、业务文档搜索或结构化错误诊断
`devdoc article search` 把一个完整 query 字符串传给 `search_open_platform_docs`。它不接受
doc-id、node-id、space-id 等对象标识,也没有 request-id、error-code、error-message、context
等独立字段;这些值若要搜索,需要由调用者明确组成 query。业务文档搜索应走 drive/wiki/doc。
候选 block 对象 ID、错误诊断字段和需要拼接的 context;裸 `id/type` ambiguous。它不把多个
字段合成 query,不自动切到其他产品,也不把隐藏 hint-only `devdoc search` 当真实叶。
### 5. Help、Schema 与 Skill 的公开契约存在描述漂移
冻结 Help/Skill 称默认表格输出,但全局 `--format` 默认值和 mock 实际输出均为 JSON;只有显式
`--format table` 才走 Devdoc 表格渲染。Help/Skill 又将 `--query` 标为必填,却没有完整说明
位置 keyword 与隐藏兼容 flag;Schema 则正确发布 query 可选并用 require-one-of 约束。
这属于 Help/Skill/声明来源修复,不是别名问题。第一轮落地应同步默认输出说明、检索词三入口
和正整数语义,避免 Agent 根据文档生成多余参数或误判返回形态。
## 当前别名表可以实施的方案
1. 将 `devdoc article search` 加入既有 `search_query` concept,覆盖 q、keywords、search-word。
2. 在精确 override 增加 search-term/query-text 到 query,以及 results-per-page 到 size。
3. 为一基页码 concept 增加 page-number;保留正式表已有分页 size/page 同义词。
4. 对 cursor/offset/page-index、对象 ID、错误诊断字段做安全拦截,裸 id/type 提示歧义。
5. 保持位置 keyword 与隐藏 `--keyword` 原生,不把 hint-only 或 Dev 产品兼容叶纳入本产品规则。
## 当前能力支持不了的事项
- 把 `--query-like-flag` 的值搬成位置 argv,或反向重排 argv;
- 在位置 keyword 与 `--query` 同时出现时自动报冲突或选择另一方;
- 把多个 error/context/API 字段拼接成一个检索 query;
- 验证、转换或修复 page/size 数值,区分非法值与显式默认值;
- 把 offset/cursor/page-token 换算成一基页码;
- 根据 doc-id、URL 或业务文档意图自动切到 drive/wiki/doc;
- 把 `devdoc search` hint-only 节点升级成真实别名命令;
- 修改默认输出格式,或修复 Help/Skill/Schema 文案漂移;
- 把同接口的 `dev doc search` 跨产品规则一并扩大。
## 第一轮改造建议
第一轮建议落地 1 个 concept scope 扩展、1 个 page-number member 和 1 个 scope_strict override。
同时修改 Devdoc Help/Skill:明确真实默认 JSON、显式 table、位置 keyword/公开 query 的二选一,
以及 page/size 正整数规则。Runtime 应另加非法数值显式错误与双输入冲突校验;这些代码修复不应
由 alias 表代替。
## 候选 `param_concepts.json` 改动与审核
- `search_query.commands` 仅新增 `devdoc article search`;
- `page_number.members` 仅新增 `page-number`,该 concept 仍只作用于 Devdoc 正式叶;
- 新增 1 个 `scope_strict` command override;
- 新增 17 个 fixture:8 active、9 block/ambiguous;
- 8 active 中 7 条对应新增生成 alias,1 条复核既有 max-results;原生 hidden keyword 通过独立
行为测试验证,不伪装成中央 alias fixture;
- 生成作用域仍为 569,Devdoc entry 从 10 alias、4 blocked、0 ambiguous 变为
17 alias、23 blocked、2 ambiguous;fallback 无变化;
- 自动 alias 的来源均不是真实 flag,目标均为真实 canonical flag;值域、角色、基数和单位一致;
- guard 与真实可见/隐藏/全局 flags 冲突为 0;
- 删除本产品改动后,非目标 JSON 结构与冻结正式表恒等;
- `devdoc search` 是 hint-only,`dev doc search` 属于 Dev 产品,均未被误纳入候选范围。
审核结论:规则业务上合理,生成、最终 payload 与完整应用门禁均通过,可进入正式落地评审。候选位置:
`docs/parameter-hallucination/devdoc/param_concepts.json`。
## 验证结果与正式替换前置条件
| 验证项 | 结果 | 说明 |
|---|---|---|
| JSON 解析与生成器 | 通过 | 569 个命令作用域;Devdoc 17 alias、23 blocked、2 ambiguous |
| PreParse 与 alias/canonical | 通过 | 9 组最终 dry-run payload 完全一致 |
| block/ambiguous | 通过 | 9 组代表错误均在 MCP dispatch 前停止 |
| 原生入口 | 通过 | 位置 keyword、隐藏 `--keyword` 与 query payload 一致 |
| 数值边界 | 已确认缺口 | 非数字或非正 page/size 静默回退 1/10 |
| 双入口边界 | 已确认缺口 | 位置词与 query 同时出现时 query 静默覆盖 |
| 非目标回归 | 通过 | 结构恒等;generated diff 仅 Devdoc entry;fallback 无变化 |
| `internal/cli`、`internal/pipeline` | 通过 | 最终候选:CLI 81.968 秒;pipeline 0.472 秒 |
| generated drift | 通过 | 参数别名与 Schema 双次装配确定 |
| Schema Catalog 政策 | 通过 | 28 产品、1166 工具;Runtime confirmation truth 通过 |
| 完整 `internal/app` | 通过 | 295.860 秒;既有 Devdoc complete-command 模板覆盖新增 active fixture |
正式替换时应一并评审 Help/Skill 输出与必填描述;数值和双输入是否与 alias 同 PR 修复,由
Runtime 维护者决定,但不能把这些行为描述为候选已经解决。
## 分析依据
- 冻结提交:`aa4ae9a90323aa97e5cebdb5045b129f0f14e0df`,提交时间
2026-08-20 10:53:27 +08:00;
- 正式表 SHA-256:
`e41e7908c26dfdcc23636d27661d705416c001d67a6d0d5d658b1ca4bcc815c1`;
- 候选 SHA-256:
`1aa25a72e583aca9853beb262316e8e11543fcde0e0b2dc59ccd070484b78b82`;
- 命令实现:`internal/helpers/devdoc.go`、`helpers.go`;
- Skill:dingtalk-misc `references/devdoc.md` 与 `devdoc-intent-guide.md`;
- Schema:同一冻结二进制运行时声明组装的完整 leaf;interface property 和 number 类型均来自声明;
- 官方树边界:1 个 Devdoc Agent 叶、1 个隐藏 hint-only 节点;同接口 Dev 叶不属于本产品;
- 明确未使用:固定 Catalog、历史 badcase、评测工作簿、用户 Shortcut、已安装插件。
## 可复用分析流程
先把 Agent 叶、隐藏兼容 flag、位置参数、hint-only 节点和跨产品同接口叶分层;再逐项核对
CLI type、interface type、默认值、require-one-of 与 Runtime 转换;仅对可原值传递的同角色名称
开放 alias,对分页模型、对象 ID、结构化字段和数值转换 fail-closed;最后用 dry-run payload、
完整应用测试和仓库政策共同决定落地状态,并把 Help/Skill 漂移单列为来源修复。
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,167 @@
# Ding 产品 CLI 参数幻觉分析
## 结论摘要
本分析以线上 `origin/main` 冻结提交
`aa4ae9a90323aa97e5cebdb5045b129f0f14e0df` 为唯一基线,使用该提交重新构建的
`dws`、运行时 Schema、Cobra Help、内置 Shortcut、DING Skill 与正式
`internal/cli/param_concepts.json`。当前工作区在任务期间发生了分支推进,但未被本分析
回退或改写;候选仍是冻结提交正式表的独立完整副本。
DING 有 7 个原子叶和 5 个注册 Shortcut,共 12 个可执行路径;运行时 Schema 发布
6 个 Agent 工具。`ding +send-by-message` 虽在运行时 Shortcut 注册表中可执行,却未挂载
到冻结提交的 reviewed Agent/Cobra source tree,参数生成器会拒绝把它作为作用域;候选
因此只治理其原子路径 `ding message send-by-message`,并把 Shortcut 差异列为待修复的
命令面问题。
主要风险是机器人 `userId` 与个人 DING `openDingTalkId` 混用、DING 的
`openDingId` 与聊天 `openMessageId` 混用、已有消息提醒与自由文本提醒混用、发送提醒
枚举与列表过滤枚举混用,以及整数 cursor 与不透明 page token 混用。候选已通过真实
生成器、PreParse、alias/canonical dry-run、block、非目标差异审计、`internal/cli`、
`internal/pipeline`、generated drift 和 Schema Catalog 政策。完整 `internal/app` 的唯一
产品相关失败是冻结仓库缺 6 个 DING 命令的 complete-command E2E 模板;正式状态为
“规则及运行链路已验证,补齐模板后方可落地”。
## 参数问题
### 1. 机器人发送与个人发送的接收人 ID 值域不同
- `ding message send --users` 是机器人 DING 的接收人 `userId` 列表,并要求
`--robot-code`。
- `ding message send-personal`、`ding +send-personal` 和
`ding message send-by-message` 的 `--users` 是 `openDingTalkId` 列表,不需要
`robot-code`。
- 两组命令的真实 flag 都叫 `users`,但值域和发送身份不同,不能用通用
`--user-ids` 在两者间无条件归一。
候选在机器人发送上只接受 `receiver-user-ids`、`recipient-user-ids`、`user-ids`;在
个人发送上只接受 `receiver-open-dingtalk-ids`、`recipient-open-dingtalk-ids`、
`open-dingtalk-ids`。相反值域、staffId 和单数 ID 均在派发前拦截。
### 2. `openDingId`、`openMessageId` 与其他 ID 容易串用
机器人撤回、个人撤回和接收状态都需要一个 DING 的 `openDingId`,但真实参数分别是
`--id` 或 `--ding-id`。消息转 DING 同时需要会话 `openConversationId` 和聊天消息
`openMessageId`,这两个值不能当作撤回用的 DING ID。
候选扩展既有 `ding_id`,只在撤回和接收状态路径映射 `id`、`ding-id`、
`open-ding-id`;`message-id`、`task-id`、`uuid`、`request-id` 被拦截。消息转 DING
则把 `conversation-id`/`open-conversation-id` 精确映射到 `group`,把
`open-message-id`/`msg-id` 映射到 `message-id`。
### 3. 机器人身份只属于机器人发送和撤回
`robot-code` 是机器人应用身份,在 `ding message send` 与
`ding message recall` 上值原样传递。个人发送、个人撤回和消息转 DING 都不应接受
`robot-code`。候选把 `robot` 安全映射到机器人命令的 `robot-code`,并在个人身份路径
明确 block `robot`、`robot-code` 及 bot/robot ID 猜测。
### 4. 自由文本发送与“基于已有消息发送”不是同一输入形态
机器人发送和个人发送有 `--content`,因此 `text`、`body`、`message-content` 可在
精确命令内改名。`send-by-message` 没有正文参数,它引用一个已经存在的聊天消息;
`content`、`body`、`text`、`message` 在该命令上必须停止,不能静默改成
`message-id`。
### 5. `type` 在发送与列表命令中的枚举域不同
发送命令的 `--type` 是 `app/sms/call` 提醒方式;列表命令的 `--type` 是
`ALL/UNREAD/SEND/NEW_COMMENT/DELETED` 过滤器。候选允许发送命令使用
`remind-type`,列表命令使用 `message-type`,并相互 block;不得仅凭 flag 同名推断
枚举可互换。
### 6. 列表 cursor 是整数,不是不透明分页令牌
`ding message list` 与 `ding +list` 的 `--cursor` 是整数。候选只增加
`next-cursor → cursor` 的命令级别名;`page-token`、`next-token`、`offset`、`page`
被拦截,没有扩展通用 `page_cursor` concept。
### 7. Schema、原子命令、Shortcut 注册与 reviewed source tree 不完全同构
运行时 Schema 发布原子 `send/recall` 和 4 个 Shortcut;其他 5 个原子兼容叶仍可执行。
`+send-by-message` 已注册且可运行,但缺少 Contract/Identity,未进入 reviewed source
tree,因而不能出现在 `param_concepts.json` 作用域中。该缺口需要修复 Shortcut 声明,
不能靠候选表绕过生成器闭环校验。
## 当前别名表可以实施的方案
1. 扩展既有 `ding_id`、`robot_code`、`user_ids`、`open_dingtalk_ids`、
`open_conversation_id`、`open_message_id`、`content_text` 的精确命令范围。
2. 对接收人 ID 域、发送身份、ID 角色、正文输入、type 枚举和 cursor 类型增加命令级
alias 与 block。
3. 保持所有映射为“参数名变化、值原样传递”;不查询人员、不转换 ID、不改单复数,
不把自由文本包装为聊天消息。
4. 只治理生成器确认的 11 个 reviewed runnable 路径;不伪造
`ding +send-by-message` 的候选条目。
5. 补齐 6 个 active 命令的 complete-command 模板并修复 Shortcut Contract 后再评审
正式替换。
## 当前能力支持不了的事项
- 将姓名、手机号、staffId、userId、openDingTalkId 自动互转;
- 将单个接收人 ID 自动扩成列表,或把多个值隐式合并;
- 从 `openMessageId` 推导发送完成后的 `openDingId`;
- 把自由文本自动创建为聊天消息,再调用 send-by-message;
- 在机器人身份和个人身份之间自动选择发送路径;
- 在 `app/sms/call` 与列表过滤枚举之间转换;
- 把不透明 page token 或 offset 转为整数 cursor;
- 通过别名表为 `ding +send-by-message` 补 Contract/Identity 和 reviewed source tree 挂载。
这些场景应停止并提示真实参数,或先调用人员搜索、联系人查询、消息查询等显式命令;
不得只改参数名继续写操作。
## 第一轮改造建议
第一轮建议落地值域明确的 DING ID、机器人代码、接收人列表、会话/消息 ID、正文、
提醒方式和整数 cursor 别名,同时启用所有反向值域与角色保护。落地 PR 必须同步为以下
6 个 active 命令补 complete-command E2E 模板:`ding message recall`、
`ding +recall-personal`、`ding +send-personal`、`ding message send-personal`、
`ding message send-by-message`、`ding +list`。另行给 `ding +send-by-message` 补完整
Contract/Identity 后,才能把同一治理规则扩展到该 Shortcut。
## 候选 `param_concepts.json` 改动与审核
候选文件是冻结正式表的完整副本,不是增量片段。相对冻结正式文件:
- 修改 7 个既有 concept 的精确命令范围;
- 新增或修改 11 个 DING command override;
- 新增 15 个审核 fixture;
- `go generate ./internal/cli` 从 569 个命令作用域变为 577 个;
- 生成差异仅在 `ding` 条目,`command_path_fallbacks_generated.go` 无变化;
- alias/canonical 的机器人发送、个人发送、消息转 DING、撤回和列表 dry-run payload
完全一致;
- 值域、角色、单复数、输入形态或类型不一致的写法全部进入 block,未做不安全推断。
候选位置:`docs/parameter-hallucination/ding/param_concepts.json`。
## 验证结果与正式替换前置条件
| 验证项 | 结果 | 说明 |
|---|---|---|
| JSON 解析与真实生成器 | 通过 | `go generate ./internal/cli`,577 个命令作用域 |
| PreParse 与 alias/canonical payload | 通过 | 5 组代表命令 dry-run 最终 tool arguments 完全一致 |
| block/ambiguous | 通过 | ID 值域、身份、正文、枚举、分页混用均在 dispatch 前 `blocked_flag` |
| 原生参数 | 通过 | canonical flags 继续走 Cobra 原生路径 |
| 非目标回归 | 通过 | 生成差异仅为 DING;fallback 生成文件无变化 |
| `internal/cli`、`internal/pipeline` | 通过 | 隔离冻结副本执行,CLI 96.288 秒 |
| generated drift | 通过 | 双次别名生成和 Schema 组装 hash 一致 |
| Schema Catalog 政策 | 通过 | 28 产品、1166 工具 |
| complete-command payload 门禁 | 未通过 | 200/206 个活跃命令已有模板;DING 缺 6 个命令、9 个 active fixture 模板 |
| reviewed Shortcut 完整性 | 未通过 | `ding +send-by-message` 未进入 reviewed source tree,候选不能声明该路径 |
正式替换前必须补齐 6 个命令模板、重跑完整 `internal/app` 和政策门禁;如需治理
`ding +send-by-message`,还必须先完成其 Contract/Identity 声明。未完成前,本候选只
作为完整待审核草稿。
## 分析依据
- 冻结提交:`aa4ae9a90323aa97e5cebdb5045b129f0f14e0df`,提交时间
2026-08-20 10:53:27 +08:00。
- 正式表 SHA-256:
`e41e7908c26dfdcc23636d27661d705416c001d67a6d0d5d658b1ca4bcc815c1`。
- 候选 SHA-256:
`382e6c16c913b78bf325cf7193cca32eb8b035439af970b9df58735b133e398c`。
- Help/实现:`internal/helpers/ding.go`;Shortcut:`internal/shortcut/ding/ding.go`;
Skill:`skills/multi/dingtalk-misc/references/ding.md`。
- Schema 来源:同一冻结二进制的运行时声明组装;未使用固定 Catalog、历史 badcase、
用户 Shortcut 或已安装插件。
File diff suppressed because one or more lines are too long
@@ -0,0 +1,168 @@
# DWS Doc × Drive 参数幻觉联合分析
> 分析日期:2026-08-11
> 冻结基线:`origin/main@dde50494548f0ed4f6295b82e5cc214c074b0e30`
> 分析对象:官方 `doc`、`drive` 命令树、运行时组装 Schema、仓库内置 Skill、Shortcut 实现、正式参数概念表
> 数据边界:未使用历史 badcase、`dws-eval`、用户自定义 Shortcut、插件或固定 Schema 快照。
## 结论摘要
Doc 与 Drive 不能再完全分开治理。两个产品的命令路径不同,但共同操作文档空间中的节点、目录、工作区、权限和本地文件;其中 copy、move、delete 以及 permission add/list/update/remove 共 **7 组同名命令实际落到相同的 Doc RPC**。如果分别维护两套参数概念,最容易出现“同一个实体在 Doc 可兜底、在 Drive 不可兜底”,或把 Drive 的数字存储空间 ID 错当成 Doc 的知识库工作区 ID。
本轮在最新线上 `main` 的官方命令树中盘点 **131 个 Agent-visible 工具**,其中 Doc 90 个、Drive 41 个;合计出现 **521 个参数位**,Doc 367 个、Drive 154 个。Doc 有 94 个不同公开 flag,Drive 有 52 个,两边共有 **19 个同名 flag**。19 个同名 flag 的类型全部一致,但只有 10 个的底层 property 集合完全一致;其余 9 个受 Shortcut、本地文件处理或不同接口映射影响,说明“名字相同”不等于可以无条件跨产品合并。
联合分析识别出 **11 类需要治理的问题**。最重要的三项是:
1. `workspace` 与 `space-id` 不是同一实体。前者是知识库/文档空间工作区 ID 或 URL,后者是数字 DingDrive 存储空间 ID。正式概念表当前的 `space_id` 把两者放在同一概念中,存在跨值域错误归一风险。本轮候选将其改为严格的 `workspace_id`,并明确 block `space-id` 及其同义名称。
2. `dentryId` 与 `dentryUuid/fileId/nodeId` 不是同一值域。公开 `--node`/`--folder` 应使用后者;数字 `dentryId` 不能直接串到下一条 Doc/Drive 命令。候选允许 `dentry-uuid → node`,同时在相关 Drive 命令中阻断 `dentry-id`。
3. `drive +list`、`drive +find-file` 的 Shortcut 输出仍把多个候选 ID 投影为名为 `dentryId` 的稳定字段,而 Drive Skill 又明确要求后续命令使用 `fileId/dentryUuid`。这是输出契约问题,输入别名表无法修复,必须修改 Shortcut 投影和相应 Schema/Skill 说明。
基于正式表与收敛后的 Drive 草稿,本轮生成联合候选 `param_concepts.json`:共 **41 个 concept、165 个 command override、263 条 validation fixture**。生成后 Doc 命令得到 563 条 alias、1063 条 block、27 条 ambiguous;Drive 命令得到 384 条 alias、242 条 block、39 条 ambiguous。数量增加主要来自跨产品值域保护,而不是放宽通用 `id` 映射。
候选已在隔离的最新 `main` 副本完成生成、构建、263 条 fixture、全部 guard runtime contract、代表性最终 payload 等价、相关 Go 包、Schema policy、生成物漂移和组装确定性验证。正式 `internal/cli/param_concepts.json` 与生成文件没有被修改。正式落地时还需要同步维护最终 payload 测试模板;只替换 JSON 会因新增 Doc/Drive alias 缺少完整命令模板而触发门禁。
## 一、联合参数面现状
| 指标 | Doc | Drive | 合计/交集 |
|---|---:|---:|---:|
| Agent-visible 工具 | 90 | 41 | 131 |
| 参数位(命令 × flag) | 367 | 154 | 521 |
| 不同公开 flag | 94 | 52 | 19 个同名 |
| 同名 flag 类型一致 | — | — | 19/19 |
| 同名 flag property 集合一致 | — | — | 10/19 |
19 个共同 flag 是:`convert`、`created-from`、`created-to`、`creator-uids`、`cursor`、`extensions`、`file`、`filter-role`、`folder`、`limit`、`mime-type`、`name`、`node`、`output`、`query`、`role`、`users`、`version`、`workspace`。
这些共同名称可分成三类:
- 可安全共用概念:搜索起止时间、创建者列表、分页 cursor/limit、查询词、知识库 workspace、权限 role 等,前提是值原样传递且命令范围精确。
- 名字相同但业务角色不同:`file` 可能是本地输入文件或某个 Shortcut 本地参数;`output` 是本地下载/导出目标;`folder` 在不同接口中可能映射父目录或目标目录。
- 名字相同但必须保留边界:`node` 是远端文档空间节点;`version` 是历史版本号,不能与编辑 revision、导出 job ID、导入 task ID 或回收项 ID 合并。
## 二、Doc 与 Drive 的重叠命令
| Doc 命令 | Drive 命令 | 接口关系 | 参数关系 | 联合治理判断 |
|---|---|---|---|---|
| `doc copy` | `drive copy` | 同为 `doc.copy_document` | `node/folder/workspace` 完全一致 | 同一实体概念;优先引导使用 Drive 主入口 |
| `doc move` | `drive move` | 同为 `doc.move_document` | `node/folder/workspace` 完全一致 | 同上,保留 source/destination 角色保护 |
| `doc delete` | `drive delete` | 同为 `doc.delete_document` | `node` 一致 | 同一节点概念,写操作继续保留确认 |
| `doc permission add` | `drive permission add` | 同为 `doc.add_permission` | `node/workspace/users/role` 一致 | 用户列表、角色、节点和工作区可共同治理 |
| `doc permission list` | `drive permission list` | 同为 `doc.list_permission` | `node/workspace/limit/filter-role` 一致 | filter role 不能与授权 role 合并 |
| `doc permission update` | `drive permission update` | 同为 `doc.update_permission` | `node/workspace/users/role` 一致 | 同一概念,保持用户列表 cardinality |
| `doc permission remove` | `drive permission remove` | 同为 `doc.remove_permission` | `node/workspace/users` 一致 | 同一概念,不能把单用户值自动包装成列表 |
| `doc download` | `drive download` | 不同接口 | 共享 `node/output`;Drive 另有 storage/version/分片参数 | 只共用节点和本地输出,其他保持 Drive 独立 |
| `doc upload` | `drive upload` | 复合实现,无单一同源 RPC | 共享 `file/folder/workspace/convert`;远端名称不同 | 本地输入与远端显示名必须分开 |
| `doc info` | `drive info` | 不同接口 | 只共享 `node`;Drive 另有数字 `space-id` | 节点可共用,workspace 与 space-id 不可共用 |
Doc 中这些稳定兼容入口虽然仍被 Schema 发布,但部分属于隐藏/迁移性质的兼容命令。当前 alias 生成器只接受它定义的 runnable leaf,不能为部分隐藏 Doc compatibility 路径增加中央 alias。这个边界不会影响 Drive 主入口或公开 Doc Shortcut 的兜底,但意味着中央 JSON 暂时不能覆盖每一个历史兼容入口。
## 三、主要参数问题与处理方案
### 3.1 workspace 与数字 space-id 混用(P0)
Doc/Drive 的 `--workspace` 表示知识库或文档空间工作区;Drive 的 `--space-id` 表示数字存储空间。`drive list` 和 `drive upload` 同时拥有这两个参数,因此宽泛的 `--space` 无法安全决定目标。
联合候选用 `workspace_id` 管理 `workspace/workspace-id/knowledge-base-id/wiki-workspace-id`,并排除 `space-id/drive-space-id/storage-space-id/dingdrive-space-id`。数字空间别名继续只在精确 Drive 命令 override 中映射到 `--space-id`。这修正了正式表把两种值域放在 `space_id` 概念中的问题。
### 3.2 dentryId 与 dentryUuid/fileId/nodeId 混用(P0)
Drive Help 和 Skill 均要求公开 `--node`/`--folder` 使用 `dentryUuid` 或等价的 `fileId/nodeId`;数字 `dentryId` 是另一个标识符。候选将 `dentry-uuid` 纳入 `doc_node_id`,但将 `dentry-id` 加入排除项,并在 Drive 精确命令上 block。
仍需代码修复:`drive +list` 与 `drive +find-file` 当前输出投影用名为 `dentryId` 的字段承载 `dentryId/dentryUuid/id/fileId/nodeId` 中第一个存在的值。该字段不能作为可靠的后续输入契约;建议稳定输出改为 `fileId` 或 `dentryUuid`,数字 dentryId 如需保留则单列。
### 3.3 源节点、目标目录和目标工作区角色混用(P0)
copy/move/shortcut 同时接受源 `node` 与目标 `folder/workspace`。候选只接受角色明确的 `source-node-id`、`target-folder-id`、`destination-workspace-id`;`target-id`、`destination-id` 等无法确定目标实体的名称返回 ambiguous,`destination-node-id` 等明显错误角色直接 block。
### 3.4 搜索、分页、时间和创建者过滤不一致(P1)
Doc 与 Drive 都存在 `query/limit/cursor/created-from/created-to/creator-uids`,模型常生成 `keyword/page-size/page-token/created-after/created-before/creator-user-ids`。候选共用 `search_query`、`pagination_size`、`page_cursor`,并新增/扩展 `created_time_start`、`created_time_end`、`creator_user_ids`。所有映射只改名称,不转换时间单位、游标或列表格式。
### 3.5 权限用户和角色混用(P0/P1)
`users` 是目标协作者列表;`role` 是直接授权角色;`filter-role` 是查询筛选;`new-owner` 是单一新所有者;`reserve-role` 是原所有者保留角色;publish `permission` 是公开访问级别。候选共用 `document_permission_role`,但对 filter/new-owner/reserve/public permission 继续使用独立 scoped alias、block 或 ambiguous。
### 3.6 本地输入、输出、远端名称和正文混用(P1)
`file` 是本地输入,`output` 是本地下载/导出目标,`file-name/name` 是远端显示名,Doc `content` 是正文。候选新增 `local_output_path`,仅在安全的下载/导出命令中接受 `output-path/destination-path/save-path`;不把输入 file 与输出 output 互换,也不把裸文件路径自动当正文。
### 3.7 历史 version 与其他流程 ID 混用(P1)
Doc/Drive 的历史版本号可共同纳入 `doc_version_number`,但编辑 `revision`、导出 `job-id`、导入 `task-id`、回收 `id` 都必须独立。候选只扩大同一整数版本语义的命令范围,不做跨实体映射。
### 3.8 同名命令导致产品选路幻觉(P1)
Doc 与 Drive 的 10 组重叠命令会让模型在命令路径上犹豫。参数别名表只能在已经选定的 leaf 内改参数名,不能把 `doc copy` 自动切成 `drive copy`。该问题应通过 Schema selection、Skill 路由说明和主入口策略治理,而不是继续扩大全局参数同义词。
### 3.9 原生隐藏 flag 被接受但实现未读取(P1)
`RegisterCrossProductAliases` 已注册一些隐藏真实 flag。已确认两项问题:
- `drive permission list --page-size` 可被 Cobra 接受,但最终实现未读取该值;
- `drive upload --file-path` 可被 Cobra 接受,但处理器仍要求 canonical `--file`。
真实 flag 会先于中央别名生效,因此 JSON 无法接管。应修改命令实现读取正确 flag,或删除无效隐藏 flag,并补最终 payload 测试。
### 3.10 Shortcut 输出字段不能安全串联(P0)
`drive +list/+find-file` 的 `dentryId` 输出标签与真实值域不稳定,容易诱导模型把数字 ID 传给下一条 `--node`。这是“出参导致下一步入参幻觉”,必须在 Shortcut 投影、Schema 描述与 Skill 示例中统一修复。
### 3.11 隐藏 Doc compatibility 路径不在中央 alias 可治理面(P1)
生成器对 runnable leaf 有严格限制,部分隐藏兼容路径无法加入 concept/override。若业务要求这些旧路径也获得中央 alias,需要先调整命令可用性/生成器契约;当前更合理的做法是让 Agent 使用公开的 Drive 主入口或 Doc Shortcut。
## 四、联合候选别名表改动
候选文件:`docs/parameter-hallucination/doc-drive/param_concepts.json`。它是完整候选,不是补丁;正式文件未改。
主要变化:
1. 将 `space_id` 收敛为 `workspace_id`,只表示知识库/文档空间工作区,并严格排除数字 storage space。
2. `doc_node_id` 增加 `dentry-uuid`,排除 `dentry-id` 和 `space-id`。
3. `doc_version_number` 扩展到 Drive 的 download/download-version/revert,但保持 revision/job/task/recycle ID 分域。
4. 将创建时间上下界扩展到 Doc/Drive 搜索,统一为 `created_time_start/end`。
5. 将直接文档权限角色统一为 `document_permission_role`,不包含筛选角色、保留角色和公开 permission。
6. 新增 `creator_user_ids`,只用于 Doc/Drive 搜索中的创建者列表。
7. 新增 `local_output_path`,只用于下载/导出的本地目标路径。
8. 删除已经由 concept 统一拥有的知识库 workspace 和 output scoped alias,避免同一映射有两个来源。
9. 增加 10 条 Doc×Drive 联合 fixture,覆盖跨值域映射与保护边界。
候选结构审计通过:41 concepts、165 overrides、263 fixtures;非 Doc/Drive 的 override、fixture 和概念语义没有被改变。
## 五、当前能力无法解决或不应解决
| 问题 | 为什么别名表不能处理 | 建议位置 |
|---|---|---|
| numeric dentryId 转 dentryUuid/fileId | 需要查询或读取正确出参,不是改名 | Shortcut 输出/接口响应处理 |
| storage space 与 workspace 转换 | 两套值域,需要业务查询 | 命令编排或 resolver |
| Doc/Drive 同名命令选路 | alias 只处理已选 leaf 的参数 | Schema selection、Skill |
| `+list/+find-file` 输出标签错误 | 属于出参契约 | Shortcut 投影、Schema、Skill |
| hidden `page-size/file-path` 未被实现读取 | 已是真实 Cobra flag,中央 alias 不接管 | 命令实现/原生 alias 注册 |
| 隐藏 Doc compatibility 命令不被生成器接受 | 当前生成器只处理其 runnable leaf 集合 | 命令可用性或生成器契约 |
| content-file、URL、列表包装、时间/单位转换 | 涉及值转换或外部读取 | 类型化转换器或保持 block |
| `target-id/space/id` 等多目标名称 | 无法仅凭名称唯一选定 canonical | ambiguous,要求补充上下文 |
## 六、验证范围与结果
验证在 `/private/tmp` 的最新 `origin/main` 隔离副本中进行,候选只临时替换正式输入,未触发真实业务写调用。
已通过:
- `go generate ./internal/cli`,生成 319 个命令的参数规则;连续两次生成 hash 一致;
- 263 条 validation fixture 的最终 delivery path 测试;
- 全部 reviewed block/ambiguous guard 到 runtime contract 的测试;
- 代表性 alias/canonical 最终 payload 等价,包括 `dentry-uuid → node`、`knowledge-base-id → workspace`、`created-after → created-from`、`creator-user-ids → creator-uids`、`permission-role → role`;
- `internal/app`、`internal/cli`、`internal/helpers`、`internal/pipeline`、`internal/shortcut/doc`、`internal/shortcut/drive` 和 alias generator 包测试;
- Schema Catalog policy、runtime confirmation truth、generated drift、Schema assembly determinism;
- 正式 `internal/cli/param_concepts.json` 与 `internal/cli/param_aliases_generated.go` 保持无差异。
正式合入还需同步:为新增 active alias 补齐完整命令模板,并删除失去 active fixture 的旧模板。隔离验证已临时完成这些测试维护并证明可通过,但本轮交付只保存候选 JSON 和分析材料。
## 七、建议实施顺序
1. P0:先合入 `workspace_id` 与 storage `space-id` 分域、`dentry-id` block、source/destination 角色保护。
2. P0:单独修复 `drive +list/+find-file` 的稳定输出字段,确保后续命令拿到 `fileId/dentryUuid`。
3. P1:合入搜索、分页、创建时间、创建者、权限角色和本地输出路径的共用概念。
4. P1:修复 `page-size/file-path` 两个 accepted-but-ignored 原生 flag。
5. 补齐 payload 测试模板后,在正式分支运行全量生成、app、Schema、漂移和策略门禁,再替换正式别名表。
6. 在 Schema selection 与 Skill 中明确:Drive 是文件/目录管理主入口,Doc 是文档内容与结构操作主入口;参数表不承担跨产品命令选路。
@@ -0,0 +1,491 @@
{
"$schema": "./param_concepts.schema.json",
"version": 1,
"morphological_rules": {
"kebab_camel_equivalence": {"desc":"--page-size == --pageSize","enabled":true},
"separator_normalization": {"desc":"-, _, . are equivalent separators","enabled":true},
"trailing_id_tolerance": {"desc":"--base tolerates --base-id when only one is a real flag on the command","enabled":true,"guard":"the two must not both be real flags with different semantics"},
"pluralization": {"desc":"--id<->--ids, --user<->--users","enabled":false,"reason":"singular/list semantics can differ; handled by concept+intersection or command override instead"}
},
"concepts": {
"search_query": {"denotes":"search keyword string","canonical_hint":"query","members":["query","keyword","keywords","q","search-word"],"excludes":["name","subject","text","title"],"commands":["aitable +base-search","contact +dept-members","contact +resolve-dept","contact +search-user","doc +create-from-template","doc +find-doc","doc +search","doc +template-search","doc template search","drive +find-file","drive +search","drive +search-docs","drive search","mail +find-mail-user","mail user search","oa +search-forms","oa approval search-forms"],"risk":"green"},
"pagination_size": {"denotes":"returned item count upper bound","canonical_hint":"limit","members":["limit","size","page-size","max-results","max-result","take","top","per-page"],"excludes":["count","page","cursor"],"commands":["aitable record query","calendar event list","chat message list","devdoc article search","doc +comment-list","doc +find-doc","doc +list","doc +search","doc +template-list","doc +template-search","doc +version-list","doc comment list","doc template list","doc template search","doc version list","drive +recent","drive +search","drive +search-docs","drive list","drive list-spaces","drive permission list","drive recent","drive recycle list","drive search","drive star list","mail thread list","oa +list-executed"],"risk":"green"},
"page_number": {"denotes":"one-based page number","canonical_hint":"page","members":["page","page-no","current-page","page-num"],"excludes":["cursor","page-index","page-size","page-token"],"commands":["devdoc article search"],"risk":"green"},
"page_cursor": {"denotes":"pagination cursor/token","canonical_hint":"cursor","members":["cursor","next-cursor","page-token","next-token","next-page-token"],"excludes":["page","offset"],"commands":["calendar event list","doc +comment-list","doc +list","doc +search","doc +template-list","doc +template-search","doc +version-list","doc comment list","doc template list","doc template search","doc version list","drive +recent","drive +search","drive list","drive list-spaces","drive recent","drive recycle list","drive search","drive star list"],"risk":"green"},
"content_text": {"denotes":"text body content","canonical_hint":"text","members":["text","content","body"],"excludes":["title","name"],"commands":["doc +checkpoint-update","doc +comment-create","doc +comment-reply","doc +comment-update","doc +create","doc +doc-append","doc block insert","doc block update","doc comment create","doc comment create-inline","doc comment reply","doc comment update","doc create"],"risk":"green"},
"time_start": {"denotes":"start time point with unchanged value format and unit","canonical_hint":"start","members":["start","start-time","start-date","from","from-date","begin","since","time-min","min-time"],"excludes":["date","time","end"],"commands":["calendar event list","chat message list-all","report list"],"risk":"yellow"},
"time_end": {"denotes":"end time point with unchanged value format and unit","canonical_hint":"end","members":["end","end-time","end-date","time-max","max-time"],"excludes":["date","time","start"],"commands":["calendar event list"],"risk":"yellow"},
"base_id": {"denotes":"multi-dimensional table Base id","canonical_hint":"base-id","members":["base","base-id","base-token"],"excludes":[],"commands":["aitable +field-get","aitable +list-tables","aitable +record-query","aitable +record-share-url","aitable +table-get"],"risk":"green"},
"dept_id": {"denotes":"single department id","canonical_hint":"dept","members":["dept","dept-id","department","department-id","parent","parent-id"],"excludes":["depts","dept-ids","department-ids","name","query"],"commands":["contact +list-sub-depts","contact dept list-children"],"risk":"yellow"},
"dept_ids": {"denotes":"department id list","canonical_hint":"dept-ids","members":["depts","dept-ids","department-ids"],"excludes":["dept","dept-id","department-id","name","query"],"commands":["contact +list-dept-members"],"risk":"yellow"},
"group_id": {"denotes":"single DingTalk numeric groupId","canonical_hint":"group-id","members":["group-id"],"excludes":["group","conversation-id","chat","chat-id","open-conversation-id","conversation-ids","open-conversation-ids","group-name","name","id"],"commands":["chat +chat-get-by-id","chat group get-by-group-id"],"risk":"yellow"},
"open_conversation_id": {"denotes":"single DingTalk openConversationId with unchanged value","canonical_hint":"conversation-id","members":["group","conversation-id","chat","chat-id","open-conversation-id"],"excludes":["group-id","group-ids","conversation-ids","open-conversation-ids","group-name","name","id","source","target","src-conversation-id","dest-conversation-id"],"commands":["chat +category-add-conversation","chat +category-remove-conversation","chat +chat-add-bot","chat +chat-audit-join","chat +chat-bots","chat +chat-dismiss","chat +chat-invite-url","chat +chat-members-get","chat +chat-mute","chat +chat-mute-member","chat +chat-quit","chat +chat-remove-bot","chat +chat-role-add","chat +chat-role-list","chat +chat-role-query-user","chat +chat-role-remove","chat +chat-role-remove-user","chat +chat-role-set-user","chat +chat-role-update","chat +chat-set-admin","chat +chat-set-history","chat +chat-transfer-owner","chat +chat-update-alias","chat +chat-update-icon","chat +chat-update-nick","chat +chat-update-settings","chat +conversation-clear-messages","chat +conversation-clear-red-point","chat +conversation-hide","chat +conversation-info","chat +conversation-mark-read","chat +conversation-mark-unread","chat +conversation-mute","chat +flag-cancel","chat +flag-create","chat +messages-add-emoji","chat +messages-add-text-emotion","chat +messages-list-pin","chat +messages-read-status","chat +messages-recall-by-bot","chat +messages-remove-emoji","chat +messages-remove-text-emotion","chat +messages-reply","chat +messages-resource-download","chat +messages-resource-url","chat +messages-send-by-bot","chat +messages-set-pin","chat +messages-set-top","chat +messages-unset-pin","chat +messages-unset-top","chat category add-conv","chat category remove-conv","chat chmod","chat clear-messages","chat clear-red-point","chat conversation-info","chat group audit-join-validation","chat group bots","chat group dismiss","chat group invite-url","chat group members","chat group members add","chat group members add-bot","chat group members list-by-ids","chat group members remove","chat group members remove-bot","chat group notice create","chat group notice edit","chat group notice get","chat group notice list","chat group quit","chat group rename","chat group set-admin","chat group set-history","chat group transfer-owner","chat group update-alias","chat group update-icon","chat group update-nick","chat group update-settings","chat group-mute","chat group-mute-member","chat group-role add","chat group-role list","chat group-role query-user","chat group-role remove","chat group-role remove-user","chat group-role set-user","chat group-role update","chat hide","chat mark-read","chat mark-unread","chat message add-favorite","chat message download-media","chat message list","chat message list-mentions","chat message list-pin-msg","chat message list-topic-replies","chat message read-status","chat message recall","chat message recall-by-bot","chat message remove-favorite","chat message reply","chat message search","chat message send","chat message send-by-bot","chat message send-card","chat message set-pin-msg","chat message set-top-msg","chat message unset-pin-msg","chat message unset-top-msg","chat mute-at-all","chat mute-red-envelope","chat set-top"],"risk":"yellow"},
"open_conversation_ids": {"denotes":"DingTalk openConversationId list with unchanged element values","canonical_hint":"conversation-ids","members":["conversation-ids","open-conversation-ids","groups"],"excludes":["group-id","group-ids","conversation-id","open-conversation-id","chat-id"],"commands":["chat message search-advanced"],"risk":"yellow"},
"group_name": {"denotes":"group-name search keyword, not a group identifier","canonical_hint":"group-name","members":["group-name"],"excludes":["group-id","conversation-id","open-conversation-id","chat-id","id"],"commands":["chat +group-members","chat +send-to-group"],"risk":"yellow"},
"open_message_id": {"denotes":"single DingTalk openMessageId with unchanged value","canonical_hint":"open-message-id","members":["msg-id","message-id","open-message-id"],"excludes":["msg-ids","message-ids","open-message-ids","ref-msg-id","src-msg-id","open-task-id","topic-id","resource-id"],"commands":["chat +conversation-mark-read","chat +flag-cancel","chat +flag-create","chat +messages-add-emoji","chat +messages-add-text-emotion","chat +messages-forward","chat +messages-read-status","chat +messages-remove-emoji","chat +messages-remove-text-emotion","chat +messages-resource-download","chat +messages-set-pin","chat +messages-set-top","chat +messages-unset-pin","chat +messages-unset-top","chat mark-read","chat message add-emoji","chat message add-favorite","chat message add-text-emotion","chat message download-media","chat message forward","chat message read-status","chat message recall","chat message remove-emoji","chat message remove-favorite","chat message remove-text-emotion","chat message set-pin-msg","chat message set-top-msg","chat message unset-pin-msg","chat message unset-top-msg"],"risk":"yellow"},
"open_message_ids": {"denotes":"DingTalk openMessageId list with unchanged element values","canonical_hint":"msg-ids","members":["msg-ids","message-ids","open-message-ids"],"excludes":["msg-id","message-id","open-message-id","ref-msg-id","src-msg-id"],"commands":["chat +flag-cancel","chat +flag-create","chat +messages-combine-forward","chat +messages-mget","chat message combine-forward","chat message list-by-ids","chat message list-emotion-replies"],"risk":"yellow"},
"referenced_open_message_id": {"denotes":"referenced DingTalk openMessageId in a reply","canonical_hint":"ref-msg-id","members":["ref-msg-id","ref-message-id"],"excludes":["msg-id","message-id","open-message-id","msg-ids","src-msg-id"],"commands":["chat +messages-reply","chat message reply"],"risk":"yellow"},
"user_id": {"denotes":"single user id","canonical_hint":"user-id","members":["user","user-id","userid","uid","staff-id"],"excludes":["at-user-ids","to-user","users","user-ids","name"],"commands":["chat +chat-role-query-user","chat +chat-role-set-user","chat +messages-list-direct","chat chmod","chat conversation-info","chat group transfer-owner","chat group-role query-user","chat group-role remove-user","chat group-role set-user","chat message list","chat message send","contact user profile get"],"risk":"yellow"},
"user_ids": {"denotes":"user id list","canonical_hint":"user-ids","members":["users","user-ids"],"excludes":["user","user-id","userid","uid","staff-id","at-user-ids"],"commands":["attendance +check-result","attendance check result","chat +messages-batch-send-by-bot","chat group members remove","chat group set-admin","chat group-mute-member","chat message read-status","chat message search-advanced","chat message send-by-bot"],"risk":"yellow"},
"open_dingtalk_ids": {"denotes":"DingTalk openDingTalkId list with unchanged element values","canonical_hint":"open-dingtalk-ids","members":["open-dingtalk-ids"],"excludes":["user","user-id","user-ids","staff-id","users"],"commands":["chat +chat-members-get","chat category create-smart","chat group members list-by-ids","chat message send-by-bot"],"risk":"yellow"},
"ding_id": {"denotes":"DING id","canonical_hint":"ding-id","members":["ding-id","open-ding-id"],"commands":["ding message receiver-status"],"risk":"yellow"},
"folder_id": {"denotes":"drive folder id","canonical_hint":"folder","members":["folder","folder-id"],"excludes":["space-id"],"commands":["drive +copy","drive +move","drive commit","drive copy","drive list","drive mkdir","drive move","drive shortcut","drive upload","drive upload-info","mail folder update"],"risk":"green"},
"app_id": {"denotes":"application id","canonical_hint":"unified-app-id","members":["app-id","unified-app-id","application-id"],"excludes":["app-key","app-secret","agent-id"],"commands":["dev app get"],"risk":"yellow"},
"robot_code": {"denotes":"robot code","canonical_hint":"robot-code","members":["robot-code","robot"],"excludes":["robot-id","bot-id","open-bot-id","bot-code"],"commands":["chat +chat-add-bot","chat +messages-batch-recall-by-bot","chat +messages-batch-send-by-bot","chat +messages-recall-by-bot","chat +messages-send-by-bot","chat group members add-bot","chat message recall-by-bot","chat message send-by-bot","ding message send"],"risk":"yellow"},
"open_bot_id": {"denotes":"single DingTalk openBotId with unchanged value","canonical_hint":"bot-id","members":["bot-id","open-bot-id"],"excludes":["robot-code","robot","robot-id","bot-code"],"commands":["chat +chat-remove-bot","chat group members remove-bot"],"risk":"yellow"},
"doc_node_id": {"denotes":"single DingTalk document nodeId or accepted document URL/token with unchanged value","canonical_hint":"node","members":["node","node-id","doc","doc-id","file-id","document-id","url","dentry-uuid"],"excludes":["id","folder","folder-id","parent-id","workspace","workspace-id","block-id","comment-id","comment-key","job-id","task-id","template-id","version","revision","dentry-id","space-id"],"commands":["doc +access-change","doc +access-grant","doc +access-revoke","doc +background-delete","doc +background-update","doc +checkpoint-update","doc +comment-create","doc +comment-delete","doc +comment-list","doc +comment-reply","doc +comment-update","doc +copy","doc +doc-append","doc +export","doc +export-submit","doc +fetch","doc +history-list","doc +history-revert","doc +history-save","doc +inspect","doc +move","doc +review","doc +version-list","doc +version-revert","doc +version-save","doc block delete","doc block insert","doc block list","doc block update","doc comment create","doc comment create-inline","doc comment delete","doc comment list","doc comment reply","doc comment update","doc export","doc info","doc media download","doc media insert","doc media upload","doc read","doc style background clear","doc style background set","doc style cover clear","doc style cover set","doc style get","doc update","doc version list","doc version revert","doc version save","doc whiteboard insert"],"risk":"yellow"},
"doc_comment_key": {"denotes":"single DingTalk document commentKey with unchanged value","canonical_hint":"comment-key","members":["comment-key","comment-id"],"excludes":["id","node","node-id","doc-id","block-id"],"commands":["doc +comment-delete","doc +comment-reply","doc +comment-update","doc comment delete","doc comment reply","doc comment update"],"risk":"yellow"},
"doc_version_number": {"denotes":"single document-space node historical version number with unchanged integer value","canonical_hint":"version","members":["version","version-number","version-no"],"excludes":["revision","id","node","node-id","doc-id"],"commands":["doc +history-revert","doc +version-revert","doc version revert","drive download","drive download-version","drive revert"],"risk":"yellow"},
"doc_content_format": {"denotes":"DingTalk document body format with unchanged markdown/jsonml value","canonical_hint":"content-format","members":["content-format","doc-format"],"excludes":["format","export-format","mime-type"],"commands":["doc +create","doc +update","doc create","doc update"],"risk":"green"},
"doc_edit_revision": {"denotes":"single optimistic-concurrency revision for a document edit","canonical_hint":"revision","members":["revision","expected-revision"],"excludes":["version","version-number","version-no"],"commands":["doc +update","doc update"],"risk":"yellow"},
"drive_recycle_item_id": {"denotes":"single Drive recycle-bin item ID returned by recycle list","canonical_hint":"id","members":["id","recycle-item-id","trash-item-id","deleted-item-id"],"excludes":["node","node-id","file-id","folder-id","space-id","workspace-id"],"commands":["drive recycle restore"],"risk":"yellow"},
"drive_modified_time_start": {"denotes":"Drive search modified-time lower bound in unchanged millisecond timestamp unit","canonical_hint":"modified-from","members":["modified-from","modified-after","modify-time-from","modified-time-start"],"excludes":["modified-to","created-from","created-to","start","from"],"commands":["drive +search","drive search"],"risk":"yellow"},
"drive_modified_time_end": {"denotes":"Drive search modified-time upper bound in unchanged millisecond timestamp unit","canonical_hint":"modified-to","members":["modified-to","modified-before","modify-time-to","modified-time-end"],"excludes":["modified-from","created-from","created-to","end","to"],"commands":["drive +search","drive search"],"risk":"yellow"},
"drive_file_size_bytes": {"denotes":"file size in bytes passed unchanged","canonical_hint":"file-size","members":["file-size","file-size-bytes","size-bytes","content-length"],"excludes":["part-size","page-size","limit","size"],"commands":["drive commit","drive upload-info"],"risk":"yellow"},
"drive_sort_direction": {"denotes":"Drive result sort direction with unchanged asc/desc enum","canonical_hint":"order","members":["order","sort","sort-direction","order-direction"],"excludes":["order-by","sort-by","order-field"],"commands":["drive list","drive star list"],"risk":"green"},
"workspace_id": {"denotes":"single knowledge-base or document-space workspace ID/URL passed unchanged; never a numeric DingDrive storage space ID","canonical_hint":"workspace","members":["workspace","workspace-id","knowledge-base-id","wiki-workspace-id"],"excludes":["space","space-id","drive-space-id","storage-space-id","dingdrive-space-id","folder","folder-id","node","node-id","dentry-id"],"commands":["doc +access-change","doc +access-grant","doc +access-revoke","doc +copy","doc +create","doc +create-from-template","doc +grant-and-share","doc +import","doc +list","doc +move","doc create","doc file create","doc import","doc template apply","drive +copy","drive +move","drive copy","drive list","drive move","drive permission add","drive permission list","drive permission remove","drive permission transfer-owner","drive permission update","drive shortcut","drive upload"],"risk":"yellow"},
"created_time_start": {"denotes":"Doc/Drive search created-time lower bound in unchanged millisecond timestamp unit","canonical_hint":"created-from","members":["created-from","created-after","create-time-from","created-time-start"],"excludes":["created-to","modified-from","modified-to","start","from"],"commands":["doc +search","drive +search","drive search"],"risk":"yellow"},
"created_time_end": {"denotes":"Doc/Drive search created-time upper bound in unchanged millisecond timestamp unit","canonical_hint":"created-to","members":["created-to","created-before","create-time-to","created-time-end"],"excludes":["created-from","modified-from","modified-to","end","to"],"commands":["doc +search","drive +search","drive search"],"risk":"yellow"},
"document_permission_role": {"denotes":"direct document-space permission role on grant/apply/update, passed unchanged","canonical_hint":"role","members":["role","permission-role","access-role","member-role"],"excludes":["filter-role","reserve-role","permission","public-permission"],"commands":["doc +access-change","doc +access-grant","doc +grant-and-share","drive permission add","drive permission apply","drive permission update"],"risk":"yellow"},
"creator_user_ids": {"denotes":"creator userId list used to filter Doc/Drive search results, passed unchanged","canonical_hint":"creator-uids","members":["creator-uids","creator-user-ids","creator-ids","created-by-user-ids"],"excludes":["user","user-id","user-ids","users","owner-id","modifier-uids"],"commands":["doc +search","drive +search","drive search"],"risk":"yellow"},
"local_output_path": {"denotes":"local destination file or directory path for a download/export result, passed unchanged","canonical_hint":"output","members":["output","output-path","destination-path","save-path"],"excludes":["file","file-path","folder","folder-id","content-file"],"commands":["doc +export","doc +export-get","doc +media-download","doc +resource-download","doc read","drive download","drive download-version"],"risk":"green"}
},
"command_overrides": {
"doc +version-list": {"ambiguous":["size","max-results","max-result","take","top","per-page","next-cursor","next-token","next-page-token"],"note":"--limit/--cursor and the shipped visible compatibility flags --page-size/--page-token remain native. Other pagination spellings cannot choose between two visible real flags and must stop before execution."},
"chat group rename": {"bind":{"id":"open_conversation_id"},"note":"This command's real --id carries one openConversationId; aliases reduce to --id without changing the value."},
"chat group members": {"bind":{"id":"open_conversation_id"}},
"chat group members add": {"bind":{"id":"open_conversation_id"},"block":["user-id","open-dingtalk-id"],"note":"The real --users is a list and may contain mixed userId/openDingTalkId values; singular inputs are not promoted automatically."},
"chat group members remove": {"bind":{"id":"open_conversation_id"}},
"chat message add-emoji": {"scoped_aliases":{"chat-id":"conversation-id","open-conversation-id":"conversation-id"},"block":["group-id","group-ids","conversation-ids","open-conversation-ids"],"note":"Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat message add-text-emotion": {"scoped_aliases":{"chat-id":"conversation-id","open-conversation-id":"conversation-id"},"block":["group-id","group-ids","conversation-ids","open-conversation-ids"],"note":"Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat message remove-emoji": {"scoped_aliases":{"chat-id":"conversation-id","open-conversation-id":"conversation-id"},"block":["group-id","group-ids","conversation-ids","open-conversation-ids"],"note":"Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat message remove-text-emotion": {"scoped_aliases":{"chat-id":"conversation-id","open-conversation-id":"conversation-id"},"block":["group-id","group-ids","conversation-ids","open-conversation-ids"],"note":"Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat mute": {"scoped_aliases":{"group":"conversation-id","chat-id":"conversation-id","open-conversation-id":"conversation-id"},"block":["group-id","group-ids","conversation-ids","open-conversation-ids"],"note":"Native --conversation-id/--id/--chat remain unchanged; other reviewed openConversationId spellings reduce to --conversation-id."},
"drive list": {"ambiguous":["root-id","space"],"note":"Numeric --space-id and knowledge-base --workspace are distinct routes; bare --space/--root-id cannot select a domain or folder.","scoped_aliases":{"document-id":"node","dentry-uuid":"node","directory-id":"folder","drive-space-id":"space-id","storage-space-id":"space-id","dingdrive-space-id":"space-id","order-field":"order-by","sort-by":"order-by","sort-field":"order-by"},"scope_strict":true,"block":["dentry-id"]},
"drive upload": {"ambiguous":["destination-id","space","target-id"],"note":"Local file, display name, MIME, overwrite node, folder, storage space, and knowledge-base workspace remain distinct roles.","scoped_aliases":{"dentry-uuid":"node","directory-id":"folder","drive-space-id":"space-id","storage-space-id":"space-id","dingdrive-space-id":"space-id","overwrite-node-id":"node","target-folder-id":"folder","target-workspace-id":"workspace","source-file":"file","content-type":"mime-type","filename":"file-name","name":"file-name","display-name":"file-name","upload-name":"file-name"},"block":["dentry-id","document-url","output-path"],"scope_strict":true},
"ding +receiver-status": {"scoped_aliases":{"id":"ding-id"},"note":"generic id reduces to ding-id"},
"ding message receiver-status": {"scoped_aliases":{"id":"ding-id"}},
"contact user profile get": {"scoped_aliases":{"id":"staff-id","ids":"staff-id"},"note":"user-id is reduced by the user_id concept; generic id/ids bound explicitly"},
"mail folder update": {"bind":{"id":"folder_id"},"note":"this command's --id is the folder id; --folder-id reduces to --id"},
"mail message search": {"scoped_aliases":{"subject":"query"},"scope_strict":true,"note":"never globalize: mail template create has a real and different --subject"},
"calendar event list": {"scoped_aliases":{"date":"start"},"note":"reviewed against ParseISOTimeToMillis and final list_calendar_events payload; --date is normalized centrally while the command's existing hidden compatibility flags remain native fallbacks"},
"chat +bot-find": {"scoped_aliases":{"name":"query"},"scope_strict":true,"note":"On this exact bot-search shortcut, --name and --query denote the same search keyword; --name must not become a global search alias."},
"chat +chat-messages": {"scoped_aliases":{"chat":"group"},"scope_strict":true,"note":"Evaluation compatibility: --chat carries one stable openConversationId and normalizes to the existing --group identifier route."},
"chat +search-msg": {"scoped_aliases":{"chat":"group"},"scope_strict":true,"note":"Evaluation compatibility: scalar --chat carries one stable openConversationId and normalizes to the existing scalar --group filter."},
"chat bot find": {"scoped_aliases":{"name":"query"},"scope_strict":true,"note":"On this exact bot-search command, --name and --query denote the same search keyword; --name must not become a global search alias."},
"chat +bot-search": {"scoped_aliases":{"query":"name","current-page":"page"},"block":["cursor"],"scope_strict":true,"note":"Only the reviewed bot keyword and page-number spellings are accepted; cursor pagination cannot be converted to a page number."},
"chat bot search": {"scoped_aliases":{"query":"name","current-page":"page"},"block":["cursor"],"scope_strict":true,"note":"Only the reviewed bot keyword and page-number spellings are accepted; cursor pagination cannot be converted to a page number."},
"chat message list-favorites": {"scoped_aliases":{"limit":"size"},"scope_strict":true,"note":"On this exact command, both names denote the same bounded result count; the numeric value is unchanged."},
"chat +messages-list-unread-conversations": {"scoped_aliases":{"limit":"count","size":"count"},"scope_strict":true,"note":"On this exact command, limit and size both denote the returned unread-conversation count."},
"chat +unread-chats": {"scoped_aliases":{"limit":"count","size":"count"},"scope_strict":true,"note":"On this exact command, limit and size both denote the returned unread-conversation count."},
"chat message list-unread-conversations": {"scoped_aliases":{"limit":"count","size":"count"},"scope_strict":true,"note":"On this exact command, limit and size both denote the returned unread-conversation count."},
"chat +messages-list-direct": {"scoped_aliases":{"start":"time"},"block":["end"],"scope_strict":true,"note":"This exact command accepts one start boundary in yyyy-MM-dd HH:mm:ss; an end-only input cannot be represented."},
"chat message list": {"scoped_aliases":{"start":"time"},"block":["end"],"scope_strict":true,"note":"This exact command accepts one start boundary in yyyy-MM-dd HH:mm:ss; an end-only input cannot be represented."},
"chat message list-by-sender": {"scoped_aliases":{"user-id":"sender-user-id","open-dingtalk-id":"sender-open-dingtalk-id"},"block":["time"],"scope_strict":true,"note":"Only same-role sender identifiers are mapped; --time cannot supply the required RFC3339 start/end range."},
"contact +resolve-dept": {"bind":{"name":"search_query"},"note":"The real --name is a department-name search keyword and carries the search_query concept on this shortcut."},
"contact +list-sub-depts": {"block":["name","query"],"note":"--dept is an integer department id; names and search queries require a separate resolution command"},
"contact +dept-members": {"bind":{"dept":"search_query"},"scoped_aliases":{"name":"dept"},"note":"The real --dept is a department-name search keyword; search spellings come from search_query, while --name remains command-scoped."},
"chat message send": {"scoped_aliases":{"to-user":"user","file":"file-path"},"note":"Recipient and local-file-path aliases are exact to this command; obsolete file metadata flags remain unsupported."},
"chat +group-members": {"bind":{"group":"group_name"},"note":"The real --group is a group-name search keyword on this shortcut, not an identifier."},
"chat +category-create": {"scoped_aliases":{"name":"title"},"scope_strict":true,"note":"The reviewed name/title mapping preserves the category display-name value on this exact shortcut."},
"chat category create": {"scoped_aliases":{"name":"title"},"scope_strict":true,"note":"The reviewed name/title mapping preserves the category display-name value on this exact command."},
"chat +category-rename": {"scoped_aliases":{"name":"title"},"block":["category-ids"],"scope_strict":true,"note":"The display-name alias is exact; a category-id list is not accepted where one category is required."},
"chat category rename": {"scoped_aliases":{"name":"title"},"block":["category-ids"],"scope_strict":true,"note":"The display-name alias is exact; a category-id list is not accepted where one category is required."},
"chat +category-delete": {"block":["category-ids"],"note":"This command requires one category id; list cardinality is not reduced automatically."},
"chat category delete": {"block":["category-ids"],"note":"This command requires one category id; list cardinality is not reduced automatically."},
"chat category list-conversations": {"block":["category-ids"],"note":"This command requires one category id; list cardinality is not reduced automatically."},
"chat category add-conv": {"block":["category-id"],"note":"This command requires a category-id list; one id is not promoted into a batch input."},
"chat category remove-conv": {"block":["category-id"],"note":"This command requires a category-id list; one id is not promoted into a batch input."},
"chat +chat-role-update": {"block":["role-ids"],"note":"This command requires one role id; list cardinality is not reduced automatically."},
"chat group-role remove": {"block":["role-ids"],"note":"This command requires one role id; list cardinality is not reduced automatically."},
"chat group-role update": {"block":["role-ids"],"note":"This command requires one role id; list cardinality is not reduced automatically."},
"chat +chat-role-set-user": {"block":["role-id"],"note":"This command requires a role-id list; one id is not promoted into a batch input."},
"chat group-role remove-user": {"block":["role-id"],"note":"This command requires a role-id list; one id is not promoted into a batch input."},
"chat group-role set-user": {"block":["role-id"],"note":"This command requires a role-id list; one id is not promoted into a batch input."},
"chat +messages-send-by-webhook": {"scoped_aliases":{"at-user-ids":"at-users"},"scope_strict":true,"note":"Both names denote the same userId list used for @ mentions on this exact shortcut."},
"chat message send-by-webhook": {"scoped_aliases":{"at-user-ids":"at-users"},"scope_strict":true,"note":"Both names denote the same userId list used for @ mentions on this exact command."},
"doc block insert": {"block":["before-block-id"],"note":"Parent and reference roles remain distinct. --before-block-id needs both --ref-block and --where before, while role-free --block-id cannot choose parent versus reference.","scoped_aliases":{"parent-block-id":"parent-block","ref-block-id":"ref-block","reference-block-id":"ref-block"},"ambiguous":["block-id"],"scope_strict":true},
"chat message send-by-bot": {"scoped_aliases":{"at-users":"at-user-ids"},"block":["user-id","to-user-id"],"ambiguous":["at-ids"],"note":"The reviewed @ userId-list alias is exact; singular recipients are not promoted, and bare --at-ids cannot choose an identifier domain."},
"doc +export-get": {"block":["doc-id","document-id","file-id","node","node-id","task-id","url"],"note":"This command queries one export jobId. Document node identifiers and import taskId spellings are different entities and are rejected.","scoped_aliases":{"export-job-id":"job-id"},"scope_strict":true},
"doc block delete": {"block":["index"],"note":"index (position) vs node (node id) are different"},
"doc +copy": {"scoped_aliases":{"folder-id":"folder","parent-folder":"folder","parent-folder-id":"folder","parent-node-id":"folder"},"block":["parent-id"],"scope_strict":true,"note":"This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."},
"doc +list": {"scoped_aliases":{"folder-id":"folder","parent-folder":"folder","parent-folder-id":"folder","parent-node-id":"folder"},"block":["parent-id"],"scope_strict":true,"note":"This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."},
"doc +move": {"scoped_aliases":{"folder-id":"folder","parent-folder":"folder","parent-folder-id":"folder","parent-node-id":"folder"},"block":["parent-id"],"scope_strict":true,"note":"This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."},
"doc comment create": {"scoped_aliases":{"mentioned-open-conversation-ids":"mentioned-open-conversation-id","open-conversation-id":"mentioned-open-conversation-id","open-conversation-ids":"mentioned-open-conversation-id"},"block":["chat-id","chat-ids","conversation-id","conversation-ids","group-id","group-ids"],"scope_strict":true,"note":"The target is a list of mentioned openConversationIds. Explicit open-conversation spellings preserve the list value; numeric groupId and role-free conversation spellings are rejected."},
"doc comment reply": {"scoped_aliases":{"mentioned-open-conversation-ids":"mentioned-open-conversation-id","open-conversation-id":"mentioned-open-conversation-id","open-conversation-ids":"mentioned-open-conversation-id"},"block":["chat-id","chat-ids","conversation-id","conversation-ids","group-id","group-ids"],"scope_strict":true,"note":"The target is a list of mentioned openConversationIds. Explicit open-conversation spellings preserve the list value; numeric groupId and role-free conversation spellings are rejected."},
"doc comment update": {"scoped_aliases":{"mentioned-open-conversation-ids":"mentioned-open-conversation-id","open-conversation-id":"mentioned-open-conversation-id","open-conversation-ids":"mentioned-open-conversation-id"},"block":["chat-id","chat-ids","conversation-id","conversation-ids","group-id","group-ids"],"scope_strict":true,"note":"The target is a list of mentioned openConversationIds. Explicit open-conversation spellings preserve the list value; numeric groupId and role-free conversation spellings are rejected."},
"doc +comment-create": {"block":["chat-id","chat-ids","conversation-id","conversation-ids","group-id","group-ids","mentioned-open-conversation-id","mentioned-open-conversation-ids","open-conversation-id","open-conversation-ids"],"note":"This shortcut has no group-mention flag or payload field. Group-conversation spellings are blocked instead of inventing unsupported capability; route to the stable doc comment command when group mentions are required."},
"doc +comment-reply": {"block":["chat-id","chat-ids","conversation-id","conversation-ids","group-id","group-ids","mentioned-open-conversation-id","mentioned-open-conversation-ids","open-conversation-id","open-conversation-ids"],"note":"This shortcut has no group-mention flag or payload field. Group-conversation spellings are blocked instead of inventing unsupported capability; route to the stable doc comment command when group mentions are required."},
"doc media insert": {"scoped_aliases":{"ref-block-id":"ref-block","reference-block-id":"ref-block"},"block":["before-block-id","parent-block","parent-block-id"],"ambiguous":["block-id"],"scope_strict":true,"note":"Media insertion supports a reference block but no parent-block role. --before-block-id additionally needs --where before; role-free --block-id is left ambiguous."},
"doc read": {"block":["before-block-id","parent-block-id","ref-block-id","reference-block-id"],"ambiguous":["block-id"],"note":"A section read requires --scope section plus a start/end boundary. A role-free --block-id cannot be reduced to one flag without inventing the missing scope/boundary role."},
"doc export get": {"scoped_aliases":{"export-job-id":"job-id"},"block":["doc-id","document-id","file-id","node","node-id","url"],"scope_strict":true,"note":"This command queries one export jobId. Document node identifiers are rejected; native hidden --task-id remains the command's reviewed add-only compatibility alias for --job-id."},
"doc import get": {"scoped_aliases":{"import-task-id":"task-id"},"block":["doc-id","document-id","file-id","job-id","node","node-id","url"],"scope_strict":true,"note":"This command queries one import taskId. Document node identifiers and export jobId spellings are different entities and are rejected."},
"doc +share-doc": {"block":["doc","doc-id","document-id","file-id","id","node","node-id"],"note":"The real --url requires a shareable document link. Name-only normalization cannot turn a document nodeId into a URL, so identifier spellings are rejected with guidance to provide --url."},
"doc update": {"block":["version","version-no","version-number"],"note":"--revision is an optimistic-concurrency revision, not a historical document version number. Version spellings must not reduce to --revision."},
"report outbox list": {"block":["template-type"],"note":"type vs name are different fields"},
"chat group members add-bot": {"bind":{"id":"open_conversation_id"}},
"chat group members list-by-ids": {"bind":{"id":"open_conversation_id","users":"open_dingtalk_ids"},"block":["user-id","user-ids"],"note":"This command's --id carries openConversationId, while --users carries an openDingTalkId list."},
"chat group members remove-bot": {"bind":{"id":"open_conversation_id"}},
"chat +send-to-group": {"bind":{"group":"group_name"},"note":"The real --group is a group-name search keyword on this shortcut, not an identifier."},
"chat group share-invite": {"scoped_aliases":{"source-conversation-id":"source","target-conversation-id":"target"},"block":["group-id","group-ids","user","user-id","userid","uid","staff-id"],"ambiguous":["conversation-id","open-conversation-id","group","chat","chat-id","id"],"note":"A role-free conversation identifier cannot choose between source and target; --receiver requires openDingTalkId and must not accept userId spellings."},
"chat message combine-forward": {"scoped_aliases":{"src-open-cid":"src-conversation-id","dest-open-cid":"dest-conversation-id","source-conversation-id":"src-conversation-id","target-conversation-id":"dest-conversation-id","destination-conversation-id":"dest-conversation-id"},"block":["group-id","group-ids"],"ambiguous":["conversation-id","open-conversation-id","group","chat","chat-id","id"],"note":"Source and destination conversation roles are preserved; a role-free identifier is ambiguous."},
"chat message forward": {"scoped_aliases":{"src-open-cid":"src-conversation-id","dest-open-cid":"dest-conversation-id","source-conversation-id":"src-conversation-id","target-conversation-id":"dest-conversation-id","destination-conversation-id":"dest-conversation-id"},"block":["group-id","group-ids"],"ambiguous":["conversation-id","open-conversation-id","group","chat","chat-id","id"],"note":"Source and destination conversation roles are preserved; a role-free identifier is ambiguous."},
"chat message forward-topic": {"scoped_aliases":{"src-open-conversation-id":"src-conversation-id","dest-open-conversation-id":"dest-conversation-id","source-conversation-id":"src-conversation-id","target-conversation-id":"dest-conversation-id","destination-conversation-id":"dest-conversation-id","src-open-message-id":"src-msg-id","source-message-id":"src-msg-id"},"block":["group-id","group-ids","msg-id","message-id","open-message-id"],"ambiguous":["conversation-id","open-conversation-id","group","chat","chat-id","id"],"note":"Conversation and message source/destination roles are preserved; role-free identifiers are rejected."},
"chat +conversation-info": {"block":["user","user-id","userid","uid","staff-id"],"note":"This shortcut accepts --open-dingtalk-id, not userId; use stable chat conversation-info when userId resolution is needed."},
"chat group create": {"block":["user-id","open-dingtalk-id"],"note":"The real --users is a list and may contain mixed identifier domains."},
"chat +chat-set-admin": {"block":["user-id","open-dingtalk-id"],"note":"The real --users is a mixed userId/openDingTalkId list."},
"chat +messages-read-status": {"block":["user-id","open-dingtalk-id"],"note":"The real --users is a mixed userId/openDingTalkId list."},
"chat category create-smart": {"bind":{"members":"open_dingtalk_ids"},"scoped_aliases":{"title":"name"},"note":"The real --members is an openDingTalkId list; the reviewed title/name alias is exact to the category display name."},
"chat group audit-join-validation": {"ambiguous":["user","user-id","userid","uid","staff-id"],"note":"A role-free user identifier cannot choose between the required --applicant and --inviter roles."},
"chat message reply": {"block":["user","user-id","userid","uid","staff-id"],"note":"The required --ref-sender is a role-specific openDingTalkId and must not accept generic userId spellings."},
"chat message send-card": {"block":["user","user-id","userid","uid","staff-id"],"note":"The real --receiver is a role-specific openDingTalkId and must not accept generic userId spellings."},
"chat +category-add-conversation": {"block":["category-id"],"note":"The real --category-ids is a list; a singular category ID is not promoted automatically."},
"chat +category-list-conversations": {"block":["category-ids"],"note":"The real --category-id is singular; list cardinality is not reduced automatically."},
"chat +category-remove-conversation": {"block":["category-id"],"note":"The real --category-ids is a list; a singular category ID is not promoted automatically."},
"chat +chat-add-bot": {"bind":{"id":"open_conversation_id"},"note":"The real --id is the group's openConversationId; robotCode and openBotId remain different domains."},
"chat +chat-audit-join": {"scoped_aliases":{"applicant-user-id":"applicant","inviter-user-id":"inviter"},"ambiguous":["user","user-id","userid","uid","staff-id"],"scope_strict":true,"note":"A role-free user identifier cannot choose between applicant and inviter."},
"chat +chat-create": {"block":["user-id","open-dingtalk-id"],"note":"The real --users field is a list and may contain mixed identifier domains; a singular value is not promoted."},
"chat +chat-get-by-id": {"block":["group","conversation-id","chat","chat-id","open-conversation-id","conversation-ids","open-conversation-ids","group-name","name","id"],"note":"The real --group-id is numeric groupId; no CID or group-name spelling can be value-preservingly converted."},
"chat +chat-members-get": {"bind":{"id":"open_conversation_id","users":"open_dingtalk_ids"},"block":["group","group-name","user-id","user-ids"],"note":"The real --id is openConversationId and --users is an openDingTalkId list. The observed --group spelling carried a natural group name and is blocked; explicit CID spellings and --chat remain value-preserving aliases."},
"chat +chat-members-list": {"scoped_aliases":{"chat-id":"conversation-id","id":"conversation-id"},"block":["query","keyword","group-id","group-ids","conversation-ids","open-conversation-ids"],"scope_strict":true,"note":"Native --chat/--open-conversation-id stay native; member filtering by query is unsupported and group-name resolution stays on --group/--chat-query."},
"chat +chat-mute-member": {"scoped_aliases":{"user-ids":"users","open-dingtalk-ids":"users"},"block":["user","user-id","open-dingtalk-id"],"scope_strict":true,"note":"The target accepts a mixed identifier list; list spellings preserve values, but singular inputs are not promoted."},
"chat +chat-remove-bot": {"bind":{"id":"open_conversation_id"},"note":"The real --id is openConversationId; --bot-id is separately governed by open_bot_id."},
"chat +chat-role-remove": {"block":["role-ids"],"note":"The command removes one role ID; list cardinality is not reduced."},
"chat +chat-role-remove-user": {"scoped_aliases":{"user-id":"user","open-dingtalk-id":"user"},"block":["role-id"],"scope_strict":true,"note":"The single --user accepts either identifier domain; --role-ids remains a list."},
"chat +chat-transfer-owner": {"scoped_aliases":{"user-id":"new-owner","open-dingtalk-id":"new-owner"},"scope_strict":true,"note":"The only user role is the new owner, and the target accepts either userId or openDingTalkId without changing the value."},
"chat +chat-update": {"scoped_aliases":{"conversation-id":"group","open-conversation-id":"group","chat-id":"group","title":"name","new-title":"name"},"block":["id","group-id","group-ids","conversation-ids","open-conversation-ids"],"scope_strict":true,"note":"--group accepts a name or CID, so only explicit CID spellings are mapped; generic --id is blocked."},
"chat +conversation-set-top": {"scoped_aliases":{"open-conversation-id":"conversation-id","chat-id":"conversation-id","open-conversation-ids":"conversation-ids","chat-ids":"conversation-ids"},"block":["group","groups","group-id","group-ids","top","set-top"],"scope_strict":true,"note":"Singular/list cardinality stays explicit; top/set-top cannot be rewritten to the inverse --off switch."},
"chat +feed-group-query-item": {"scoped_aliases":{"open-conversation-ids":"conversation-ids","chat-ids":"conversation-ids"},"block":["group","groups","group-id","group-ids","conversation-id","open-conversation-id"],"scope_strict":true,"note":"The real field is an openConversationId list; group names and singular IDs are not converted."},
"chat +flag-list": {"scoped_aliases":{"limit":"page-size"},"block":["max","max-results","max-size","count","page","per-page"],"scope_strict":true,"note":"Native --page-size is the canonical page bound, native --size is its command-owned compatibility alias, and --limit is reviewed as value-preservingly equivalent to --page-size; total-count and page-number spellings are not equivalent."},
"chat +messages-batch-recall-by-bot": {"block":["msg-id","message-id","open-message-id","msg-ids","message-ids","open-message-ids"],"note":"--keys carries processQueryKey values returned by bot sending; it is not an openMessageId field."},
"chat +messages-combine-forward": {"scoped_aliases":{"src-open-cid":"src-conversation-id","src-open-conversation-id":"src-conversation-id","source-conversation-id":"src-conversation-id","dest-open-cid":"dest-conversation-id","dest-open-conversation-id":"dest-conversation-id","target-conversation-id":"dest-conversation-id","destination-conversation-id":"dest-conversation-id"},"block":["group-id","group-ids"],"ambiguous":["conversation-id","open-conversation-id","group","chat","chat-id","id"],"scope_strict":true,"note":"Source and destination conversation roles remain explicit; role-free CID spellings cannot choose a side."},
"chat +messages-forward": {"scoped_aliases":{"src-open-cid":"src-conversation-id","src-open-conversation-id":"src-conversation-id","source-conversation-id":"src-conversation-id","dest-open-cid":"dest-conversation-id","dest-open-conversation-id":"dest-conversation-id","target-conversation-id":"dest-conversation-id","destination-conversation-id":"dest-conversation-id","src-open-message-id":"msg-id","source-message-id":"msg-id"},"block":["group-id","group-ids"],"ambiguous":["conversation-id","open-conversation-id","group","chat","chat-id","id"],"scope_strict":true,"note":"The message role is uniquely the source message, but source/destination conversation roles cannot be inferred from a generic CID."},
"chat +messages-forward-topic": {"scoped_aliases":{"src-open-conversation-id":"src-conversation-id","source-conversation-id":"src-conversation-id","dest-open-conversation-id":"dest-conversation-id","target-conversation-id":"dest-conversation-id","destination-conversation-id":"dest-conversation-id","src-open-message-id":"src-msg-id","source-message-id":"src-msg-id"},"block":["group-id","group-ids","msg-id","message-id","open-message-id","msg-ids","message-ids","open-message-ids"],"ambiguous":["conversation-id","open-conversation-id","group","chat","chat-id","id"],"scope_strict":true,"note":"Source and destination conversation roles and the source-message role remain explicit; role-free message IDs and list cardinality are not inferred."},
"chat +messages-list": {"scoped_aliases":{"start":"time"},"block":["before","before-time","end","direction","page-all","count","max","max-results","max-size","page-size"],"scope_strict":true,"note":"start preserves the same boundary value; before/direction require multi-parameter or value transforms and page-all requires iteration. Native --conversation-id/--id/--size remain native."},
"chat +messages-recall-by-bot": {"block":["msg-id","message-id","open-message-id","msg-ids","message-ids","open-message-ids"],"note":"--keys carries processQueryKey values, not openMessageId values."},
"chat +messages-reply": {"scoped_aliases":{"msg-id":"ref-msg-id","open-message-id":"ref-msg-id"},"block":["group","msg-ids","message-ids","open-message-ids"],"scope_strict":true,"note":"The observed --group spelling carried a natural group name and is blocked. The only message role is the referenced message; plural IDs are not accepted, while --chat remains a CID alias and native --message-id stays native."},
"chat +messages-resource-download": {"block":["download-dir"],"note":"--output may be a file or directory under workspace safety rules; a download directory cannot be assumed equivalent."},
"doc +create": {"scoped_aliases":{"folder-id":"folder","parent-folder":"folder","parent-folder-id":"folder","parent-node-id":"folder"},"block":["content-file","parent-id"],"scope_strict":true,"note":"--content-format is value-preservingly normalized to --doc-format. A raw --content-file path cannot become --content without adding the required @file transform, so it is blocked with guidance to use @relative-path or stable doc create."},
"doc +create-from-template": {"scoped_aliases":{"folder-id":"folder","parent-folder":"folder","parent-folder-id":"folder","parent-node-id":"folder"},"block":["parent-id"],"scope_strict":true,"note":"This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."},
"doc +import": {"scoped_aliases":{"folder-id":"folder","parent-folder":"folder","parent-folder-id":"folder","parent-node-id":"folder"},"block":["parent-id"],"scope_strict":true,"note":"This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."},
"doc +update": {"scoped_aliases":{"mode":"command","doc-id":"node","document-id":"node","file-id":"node","node-id":"node","url":"node"},"block":["content-file"],"scope_strict":true,"note":"--mode append/overwrite is the same operation selector subset as --command and preserves its value. --content-file is blocked because +update requires @relative-path or stdin and central aliases cannot read/transform a file value."},
"doc +inspect": {"scoped_aliases":{"include-versions":"include-history"},"block":["include","include-info"],"scope_strict":true,"note":"Historical versions and history are the same optional section on this exact shortcut. Generic --include needs value-dependent flag expansion, while base document info is always returned, so those spellings are rejected with precise guidance."},
"doc +fetch": {"scoped_aliases":{"start-block":"start-block-id","end-block":"end-block-id"},"ambiguous":["block-id"],"scope_strict":true,"note":"Start/end block roles are preserved. A role-free --block-id cannot choose a range/section boundary and must stop before execution."},
"doc +media-download": {"scoped_aliases":{"doc":"node","doc-id":"node","document-id":"node","node-id":"node"},"ambiguous":["file-id","url"],"scope_strict":true,"note":"This command has both document-node and attachment-resource roles. Strong document spellings map to --node; --file-id and --url cannot safely choose between document and media identities."},
"doc +media-insert": {"scoped_aliases":{"doc":"node","doc-id":"node","document-id":"node","node-id":"node"},"ambiguous":["file-id","url"],"scope_strict":true,"note":"This command has both document-node and local-media roles. Strong document spellings map to --node; --file-id and --url cannot safely choose a role."},
"doc +media-list": {"scoped_aliases":{"doc":"node","doc-id":"node","document-id":"node","node-id":"node"},"ambiguous":["file-id","url"],"scope_strict":true,"note":"The command lists media inside one document, so only strong document spellings map to --node. File and URL spellings remain role-ambiguous."},
"doc +media-preview": {"scoped_aliases":{"doc":"node","doc-id":"node","document-id":"node","node-id":"node"},"ambiguous":["file-id","url"],"scope_strict":true,"note":"This command has both document-node and attachment-resource roles. Strong document spellings map to --node; --file-id and --url cannot safely choose a role."},
"doc +resource-delete": {"scoped_aliases":{"doc":"node","doc-id":"node","document-id":"node","node-id":"node"},"ambiguous":["file-id","url"],"scope_strict":true,"note":"This command removes a document resource while targeting the document by --node. File and URL spellings do not uniquely identify that document role."},
"doc +resource-download": {"scoped_aliases":{"doc":"node","doc-id":"node","document-id":"node","node-id":"node"},"ambiguous":["file-id","url"],"scope_strict":true,"note":"This command downloads a document resource while targeting the document by --node. File and URL spellings do not uniquely identify that document role."},
"doc +resource-update": {"scoped_aliases":{"doc":"node","doc-id":"node","document-id":"node","node-id":"node"},"ambiguous":["file-id","url"],"scope_strict":true,"note":"This command has document-node, local-file, and HTTPS image URL roles. Only strong document spellings map to --node; --file-id and --url must stop as ambiguous."},
"doc +share": {"block":["doc","doc-id","document-id","file-id","id","node","node-id"],"note":"The real --url requires a shareable document link. Name-only normalization cannot turn a node identifier into a URL, so identifier spellings are rejected with guidance to provide --url."},
"doc +grant-and-share": {"scoped_aliases":{"doc":"node","doc-id":"node","document-id":"node","file-id":"node","node-id":"node"},"scope_strict":true,"note":"This workflow has two different real URL roles: --node selects the document for access control and --url is the shareable link sent to recipients. Explicit document-ID spellings map only to --node; --url remains native."},
"drive copy": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node","directory-id":"folder","source-node-id":"node","source-file-id":"node","target-folder-id":"folder","destination-folder-id":"folder","target-workspace-id":"workspace","destination-workspace-id":"workspace"},"scope_strict":true,"note":"Source node and destination folder/workspace are different roles; role-free target IDs stop before write dispatch.","block":["dentry-id","destination-node-id","dingdrive-space-id","drive-space-id","source-folder-id","space-id","storage-space-id"],"ambiguous":["destination-id","target-id"]},
"drive cover": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node"},"scope_strict":true,"note":"The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.","block":["dentry-id"]},
"drive download-version": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node","chunk-size":"part-size","concurrency":"parallel","parallelism":"parallel"},"scope_strict":true,"note":"Output path, chunk size, and concurrency names preserve values; local input and remote folder roles remain blocked.","block":["dentry-id","file","file-path","folder","folder-id"]},
"drive move": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node","directory-id":"folder","source-node-id":"node","source-file-id":"node","target-folder-id":"folder","destination-folder-id":"folder","target-workspace-id":"workspace","destination-workspace-id":"workspace"},"scope_strict":true,"note":"Source node and destination folder/workspace are different roles; role-free target IDs stop before write dispatch.","block":["dentry-id","destination-node-id","dingdrive-space-id","drive-space-id","source-folder-id","space-id","storage-space-id"],"ambiguous":["destination-id","target-id"]},
"drive permission add": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node","member-user-ids":"users","collaborator-ids":"users","target-user-ids":"users","target-node-id":"node"},"scope_strict":true,"note":"The user list denotes target collaborators on this exact node permission command; the native singular compatibility spellings remain native.","block":["dentry-id","dingdrive-space-id","drive-space-id","space-id","storage-space-id"]},
"drive permission apply": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node","approver-user-ids":"users","approver-ids":"users","target-node-id":"node","apply-reason":"reason","notification-mode":"notify-mode"},"scope_strict":true,"note":"The user list denotes approvers, not target collaborators; values and notification enum are passed unchanged.","block":["dentry-id"]},
"drive permission apply-info": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node"},"scope_strict":true,"note":"The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.","block":["dentry-id"]},
"drive permission list": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node","role-filter":"filter-role","permission-role-filter":"filter-role","target-node-id":"node"},"scope_strict":true,"note":"Filtering by a role is not the same as granting/updating a role.","block":["dentry-id","dingdrive-space-id","drive-space-id","permission","role","space-id","storage-space-id"]},
"drive permission remove": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node","member-user-ids":"users","collaborator-ids":"users","target-user-ids":"users","target-node-id":"node"},"scope_strict":true,"note":"The user list denotes target collaborators on this exact node permission command; the native singular compatibility spellings remain native.","block":["dentry-id","dingdrive-space-id","drive-space-id","space-id","storage-space-id"]},
"drive permission transfer-owner": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node","target-node-id":"node","target-workspace-id":"workspace","old-owner-role":"reserve-role","keep-role":"reserve-role","new-owner-id":"new-owner","new-owner-user-id":"new-owner"},"scope_strict":true,"note":"Node/workspace target and new/old owner roles are distinct on this irreversible command; role-free names stop before dispatch.","block":["current-owner","dentry-id","dingdrive-space-id","drive-space-id","space-id","storage-space-id","user-ids","users"],"ambiguous":["owner","owner-user-id","target-id","user-id"]},
"drive permission update": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node","member-user-ids":"users","collaborator-ids":"users","target-user-ids":"users","target-node-id":"node"},"scope_strict":true,"note":"The user list denotes target collaborators on this exact node permission command; the native singular compatibility spellings remain native.","block":["dentry-id","dingdrive-space-id","drive-space-id","space-id","storage-space-id"]},
"drive publish get": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node"},"scope_strict":true,"note":"The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.","block":["dentry-id"]},
"drive publish set": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node","public-permission":"permission","public-role":"permission"},"scope_strict":true,"note":"Internet-public permission is not the same as a direct collaborator role.","block":["access-role","dentry-id","permission-role","role"]},
"drive publish unset": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node"},"scope_strict":true,"note":"The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.","block":["dentry-id"]},
"drive rename": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node","file-name":"name","display-name":"name","new-name":"name"},"scope_strict":true,"note":"The name is this exact folder/node display name; search query and local path are different roles.","block":["dentry-id"]},
"drive revert": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node"},"scope_strict":true,"note":"The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.","block":["dentry-id"]},
"drive shortcut": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node","directory-id":"folder","source-node-id":"node","source-file-id":"node","target-folder-id":"folder","destination-folder-id":"folder","target-workspace-id":"workspace","destination-workspace-id":"workspace"},"scope_strict":true,"note":"Source node and destination folder/workspace are different roles; role-free target IDs stop before write dispatch.","block":["dentry-id","destination-node-id","dingdrive-space-id","drive-space-id","source-folder-id","space-id","storage-space-id"],"ambiguous":["destination-id","target-id"]},
"drive star add": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node"},"scope_strict":true,"note":"The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.","block":["dentry-id"]},
"drive star remove": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node"},"scope_strict":true,"note":"The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.","block":["dentry-id"]},
"drive stats": {"scoped_aliases":{"document-id":"node","dentry-uuid":"node"},"scope_strict":true,"note":"The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.","block":["dentry-id"]},
"drive +copy": {"scoped_aliases":{"dentry-uuid":"node","directory-id":"folder","source-node-id":"node","source-file-id":"node","target-folder-id":"folder","destination-folder-id":"folder","target-workspace-id":"workspace","destination-workspace-id":"workspace"},"block":["dentry-id","destination-node-id","dingdrive-space-id","document-url","drive-space-id","source-folder-id","space-id","storage-space-id"],"scope_strict":true,"note":"Source node and destination folder/workspace are different roles; role-free target IDs stop before write dispatch.","ambiguous":["destination-id","target-id"]},
"drive +info": {"scoped_aliases":{"dentry-uuid":"node","drive-space-id":"space-id","storage-space-id":"space-id","dingdrive-space-id":"space-id"},"block":["dentry-id","document-url","knowledge-base-id","wiki-workspace-id","workspace","workspace-id"],"scope_strict":true,"note":"This command has only a numeric DingDrive space target; knowledge-base workspace spellings are a different value domain."},
"drive +move": {"scoped_aliases":{"dentry-uuid":"node","directory-id":"folder","source-node-id":"node","source-file-id":"node","target-folder-id":"folder","destination-folder-id":"folder","target-workspace-id":"workspace","destination-workspace-id":"workspace"},"block":["dentry-id","destination-node-id","dingdrive-space-id","document-url","drive-space-id","source-folder-id","space-id","storage-space-id"],"scope_strict":true,"note":"Source node and destination folder/workspace are different roles; role-free target IDs stop before write dispatch.","ambiguous":["destination-id","target-id"]},
"drive delete": {"scoped_aliases":{"dentry-uuid":"node"},"block":["dentry-id","document-url"],"scope_strict":true,"note":"This command publicly accepts an ID-only Drive node; dentry spellings preserve the value and URL-specific spellings remain protected."},
"drive download": {"scoped_aliases":{"dentry-uuid":"node","drive-space-id":"space-id","storage-space-id":"space-id","dingdrive-space-id":"space-id","chunk-size":"part-size","concurrency":"parallel","parallelism":"parallel"},"block":["dentry-id","document-url","file","file-path","folder","folder-id","knowledge-base-id","wiki-workspace-id","workspace","workspace-id"],"scope_strict":true,"note":"Output path, chunk size, and concurrency names preserve values; local input and remote folder roles remain blocked."},
"drive info": {"scoped_aliases":{"dentry-uuid":"node","drive-space-id":"space-id","storage-space-id":"space-id","dingdrive-space-id":"space-id"},"block":["dentry-id","document-url","knowledge-base-id","wiki-workspace-id","workspace","workspace-id"],"scope_strict":true,"note":"This command has only a numeric DingDrive space target; knowledge-base workspace spellings are a different value domain."},
"drive commit": {"scoped_aliases":{"directory-id":"folder","drive-space-id":"space-id","storage-space-id":"space-id","dingdrive-space-id":"space-id","parent-directory-id":"folder","upload-session-id":"upload-id","size-bytes":"file-size","name":"file-name","display-name":"file-name","filename":"file-name","upload-name":"file-name"},"scope_strict":true,"note":"Upload session, file name, file size in bytes, parent folder, and storage space remain separate roles.","block":["knowledge-base-id","wiki-workspace-id","workspace","workspace-id"]},
"drive mkdir": {"scoped_aliases":{"directory-id":"folder","drive-space-id":"space-id","storage-space-id":"space-id","dingdrive-space-id":"space-id","parent-directory-id":"folder","folder-name":"name","display-name":"name","new-name":"name"},"scope_strict":true,"note":"The name is this exact folder/node display name; search query and local path are different roles.","block":["knowledge-base-id","wiki-workspace-id","workspace","workspace-id"]},
"drive upload-info": {"scoped_aliases":{"directory-id":"folder","drive-space-id":"space-id","storage-space-id":"space-id","dingdrive-space-id":"space-id","parent-directory-id":"folder","content-type":"mime-type","size-bytes":"file-size","name":"file-name","display-name":"file-name","filename":"file-name","upload-name":"file-name"},"scope_strict":true,"note":"File metadata aliases preserve MIME and byte units; file path and upload session are different stages.","block":["knowledge-base-id","wiki-workspace-id","workspace","workspace-id"]},
"drive recycle list": {"scoped_aliases":{"drive-space-id":"space-id","storage-space-id":"space-id","dingdrive-space-id":"space-id"},"scope_strict":true,"note":"This command has only a numeric DingDrive space target; knowledge-base workspace spellings are a different value domain.","block":["knowledge-base-id","wiki-workspace-id","workspace","workspace-id"]},
"drive search": {"ambiguous":["end","from","start","time-from","time-to","to","types"],"block":["offset","page"],"scope_strict":true,"note":"Created/modified ranges and file/content type arrays are separate roles; generic time/type names are not guessed."},
"drive +search": {"ambiguous":["end","from","start","time-from","time-to","to","types"],"block":["offset","page"],"scope_strict":true,"note":"Created/modified ranges and file/content type arrays are separate roles; generic time/type names are not guessed."},
"drive recycle restore": {"bind":{"id":"drive_recycle_item_id"},"block":["file-id","folder-id","node","node-id","space-id","workspace-id"],"scope_strict":true,"note":"The real --id is a recycle-item ID from recycle list, not a normal Drive node ID."},
"drive star list": {"ambiguous":["type","types"],"scope_strict":true,"note":"Content types and resource types are separate arrays; a generic type list cannot choose one.","scoped_aliases":{"order-field":"order-by","sort-by":"order-by","sort-field":"order-by"}},
"drive recent": {"ambiguous":["type","types"],"scope_strict":true,"note":"Creator type, operation type, and file type filters are distinct and keep their enum/list forms."},
"drive +recent": {"ambiguous":["type","types"],"scope_strict":true,"note":"Creator type, operation type, and file type filters are distinct and keep their enum/list forms."}
},
"validation_fixture": {
"cases": [
{"command":"oa +search-forms","emitted":"keyword","expect":"query","via":"concept:search_query","occ":28},
{"command":"aitable +list-tables","emitted":"base-id","expect":"base","via":"concept:base_id+morph","occ":26},
{"command":"mail +find-mail-user","emitted":"keyword","expect":"query","via":"concept:search_query","occ":18},
{"command":"aitable +field-get","emitted":"base","expect":"base-id","via":"concept:base_id+morph","occ":8},
{"command":"aitable +record-query","emitted":"base","expect":"base-id","via":"concept:base_id+morph","occ":8},
{"command":"aitable +table-get","emitted":"base","expect":"base-id","via":"concept:base_id+morph","occ":8},
{"command":"doc block update","emitted":"content","expect":"text","via":"concept:content_text","occ":6},
{"command":"contact +resolve-dept","emitted":"query","expect":"name","via":"concept:search_query+bind","occ":4},
{"command":"devdoc article search","emitted":"limit","expect":"size","via":"concept:pagination_size","occ":2},
{"command":"devdoc article search","emitted":"page-size","expect":"size","via":"concept:pagination_size","occ":2},
{"command":"devdoc article search","emitted":"current-page","expect":"page","via":"concept:page_number","occ":2},
{"command":"mail message search","emitted":"subject","expect":"query","via":"override:scoped_strict","occ":4},
{"command":"aitable +record-share-url","emitted":"base","expect":"base-id","via":"concept:base_id+morph","occ":3},
{"command":"aitable record query","emitted":"max-results","expect":"limit","via":"concept:pagination_size","occ":2},
{"command":"calendar event list","emitted":"date","expect":"start","via":"override:scoped(reviewed+payload)","occ":2},
{"command":"calendar event list","emitted":"start-time","expect":"start","via":"native:reviewed-compatibility-fallback","occ":2},
{"command":"calendar event list","emitted":"min-time","expect":"start","via":"native:reviewed-compatibility-fallback","occ":2},
{"command":"calendar event list","emitted":"time-min","expect":"start","via":"native:reviewed-compatibility-fallback","occ":2},
{"command":"calendar event list","emitted":"end-time","expect":"end","via":"native:reviewed-compatibility-fallback","occ":2},
{"command":"calendar event list","emitted":"time-max","expect":"end","via":"native:reviewed-compatibility-fallback","occ":2},
{"command":"calendar event list","emitted":"max-results","expect":"limit","via":"native:reviewed-compatibility-fallback","occ":2},
{"command":"calendar event list","emitted":"next-cursor","expect":"cursor","via":"native:reviewed-compatibility-fallback","occ":2},
{"command":"calendar event list","emitted":"calendar","expect":"calendar-id","via":"native:reviewed-compatibility-fallback","occ":2},
{"command":"chat message list","emitted":"max-results","expect":"limit","via":"concept:pagination_size","occ":2},
{"command":"chat message list-by-sender","emitted":"time","expect":"did-you-mean:blocked","via":"guard:time-format-boundary","occ":2},
{"command":"doc +template-search","emitted":"keyword","expect":"query","via":"concept:search_query","occ":2},
{"command":"doc block insert","emitted":"content","expect":"text","via":"concept:content_text","occ":2},
{"command":"drive list","emitted":"folder-id","expect":"folder","via":"concept:folder_id+morph","occ":2},
{"command":"mail thread list","emitted":"max-results","expect":"limit","via":"concept:pagination_size","occ":2},
{"command":"mail user search","emitted":"query","expect":"keyword","via":"concept:search_query","occ":2},
{"command":"oa +list-executed","emitted":"take","expect":"limit","via":"concept:pagination_size","occ":2},
{"command":"oa approval search-forms","emitted":"keyword","expect":"query","via":"concept:search_query","occ":2},
{"command":"report list","emitted":"from-date","expect":"start","via":"concept:time_start","occ":2},
{"command":"aitable +base-search","emitted":"keyword","expect":"query","via":"concept:search_query","occ":1},
{"command":"contact +search-user","emitted":"keyword","expect":"query","via":"concept:search_query","occ":1},
{"command":"chat group rename","emitted":"group","expect":"id","via":"override:bind(open_conversation_id)","occ":31},
{"command":"ding +receiver-status","emitted":"id","expect":"ding-id","via":"override:scoped(ding_id)","occ":24},
{"command":"chat group members","emitted":"group","expect":"id","via":"override:bind(open_conversation_id)","occ":8},
{"command":"contact +list-sub-depts","emitted":"dept-id","expect":"dept","via":"concept:dept_id+morph","occ":4},
{"command":"contact +list-sub-depts","emitted":"name","expect":"did-you-mean:blocked","via":"guard:name-vs-id","occ":2},
{"command":"contact +list-sub-depts","emitted":"query","expect":"did-you-mean:blocked","via":"guard:query-vs-id","occ":2},
{"command":"contact user profile get","emitted":"user-id","expect":"staff-id","via":"concept:user_id","occ":2},
{"command":"contact user profile get","emitted":"id","expect":"staff-id","via":"override:scoped","occ":2},
{"command":"contact user profile get","emitted":"ids","expect":"staff-id","via":"override:scoped","occ":2},
{"command":"chat message send-by-bot","emitted":"user-id","expect":"did-you-mean:blocked","via":"guard:single-vs-list","occ":4},
{"command":"chat message send-by-bot","emitted":"to-user-id","expect":"did-you-mean:blocked","via":"guard:single-vs-list","occ":1},
{"command":"dev app get","emitted":"app-id","expect":"unified-app-id","via":"concept:app_id","occ":5},
{"command":"chat message list-all","emitted":"from","expect":"start","via":"concept:time_start","occ":2},
{"command":"chat message list-all","emitted":"start-time","expect":"start","via":"concept:time_start","occ":2},
{"command":"chat message search-advanced","emitted":"group","expect":"conversation-ids","via":"native:reviewed-single-to-list","occ":4},
{"command":"chat message send","emitted":"to-user","expect":"user","via":"override:scoped(reviewed+payload)","occ":4},
{"command":"contact +dept-members","emitted":"name","expect":"dept","via":"override:scoped(reviewed)","occ":2},
{"command":"contact +dept-members","emitted":"query","expect":"dept","via":"concept:search_query+bind","occ":2},
{"command":"contact dept list-children","emitted":"parent-id","expect":"dept","via":"concept:dept_id","occ":2},
{"command":"contact dept list-children","emitted":"parent","expect":"dept","via":"concept:dept_id","occ":2},
{"command":"ding message receiver-status","emitted":"id","expect":"ding-id","via":"override:scoped(ding_id)","occ":2},
{"command":"ding message receiver-status","emitted":"open-ding-id","expect":"ding-id","via":"concept:ding_id","occ":2},
{"command":"chat group members add","emitted":"group","expect":"id","via":"override:bind(open_conversation_id)","occ":3},
{"command":"attendance +check-result","emitted":"user-id","expect":"did-you-mean:blocked","via":"concept:user_ids+exclude","occ":2},
{"command":"attendance check result","emitted":"user-ids","expect":"users","via":"concept:user_ids","occ":2},
{"command":"chat group members remove","emitted":"group","expect":"id","via":"override:bind(open_conversation_id)","occ":2},
{"command":"chat group set-admin","emitted":"user-id","expect":"user","via":"native:reviewed-compatibility-alias","occ":2},
{"command":"ding message send","emitted":"robot","expect":"robot-code","via":"concept:robot_code","occ":2},
{"command":"doc +export-get","emitted":"node","expect":"did-you-mean:blocked","via":"override:block","occ":2},
{"command":"doc block delete","emitted":"index","expect":"did-you-mean:blocked","via":"override:block","occ":2},
{"command":"doc block insert","emitted":"before-block-id","expect":"did-you-mean:blocked","via":"guard:requires-multi-parameter-transform","occ":2},
{"command":"drive info","emitted":"workspace","expect":"did-you-mean:blocked","via":"guard:workspace-vs-numeric-storage-space","occ":2},
{"command":"mail folder update","emitted":"folder-id","expect":"id","via":"override:bind(folder_id)","occ":2},
{"command":"report outbox list","emitted":"template-type","expect":"did-you-mean:blocked","via":"override:block","occ":2},
{"command":"chat +group-members","emitted":"group-name","expect":"group","via":"concept:group_name+bind"},
{"command":"chat group get-by-group-id","emitted":"conversation-id","expect":"did-you-mean:blocked","via":"guard:open-conversation-id-vs-group-id"},
{"command":"chat group get-by-group-id","emitted":"id","expect":"did-you-mean:blocked","via":"guard:generic-id-vs-group-id"},
{"command":"chat group rename","emitted":"conversation-id","expect":"id","via":"concept:open_conversation_id+bind"},
{"command":"chat group rename","emitted":"group-id","expect":"did-you-mean:blocked","via":"guard:group-id-vs-open-conversation-id"},
{"command":"chat message send","emitted":"conversation-id","expect":"group","via":"concept:open_conversation_id"},
{"command":"chat message add-emoji","emitted":"open-conversation-id","expect":"conversation-id","via":"override:scoped"},
{"command":"chat message add-emoji","emitted":"group-id","expect":"did-you-mean:blocked","via":"guard:group-id-vs-open-conversation-id"},
{"command":"chat +group-members","emitted":"conversation-id","expect":"did-you-mean:blocked","via":"guard:group-name-vs-open-conversation-id"},
{"command":"chat +send-to-group","emitted":"group-name","expect":"group","via":"concept:group_name+bind"},
{"command":"chat message search-advanced","emitted":"open-conversation-ids","expect":"conversation-ids","via":"concept:open_conversation_ids"},
{"command":"chat message search-advanced","emitted":"group-ids","expect":"did-you-mean:blocked","via":"guard:group-id-list-vs-open-conversation-id-list"},
{"command":"chat message recall","emitted":"message-id","expect":"msg-id","via":"concept:open_message_id"},
{"command":"chat message add-favorite","emitted":"msg-id","expect":"open-message-id","via":"concept:open_message_id"},
{"command":"chat message list-by-ids","emitted":"message-ids","expect":"msg-ids","via":"concept:open_message_ids"},
{"command":"chat message list-by-ids","emitted":"message-id","expect":"did-you-mean:blocked","via":"guard:single-vs-list"},
{"command":"chat message reply","emitted":"ref-message-id","expect":"ref-msg-id","via":"concept:referenced_open_message_id"},
{"command":"chat message reply","emitted":"message-id","expect":"did-you-mean:blocked","via":"guard:message-role"},
{"command":"chat message forward-topic","emitted":"src-open-message-id","expect":"src-msg-id","via":"override:scoped-role"},
{"command":"chat message forward-topic","emitted":"message-id","expect":"did-you-mean:blocked","via":"guard:message-role"},
{"command":"chat message combine-forward","emitted":"src-open-cid","expect":"src-conversation-id","via":"override:scoped-role"},
{"command":"chat message forward-topic","emitted":"dest-open-conversation-id","expect":"dest-conversation-id","via":"override:scoped-role"},
{"command":"chat message forward","emitted":"conversation-id","expect":"did-you-mean:ambiguous","via":"guard:source-vs-destination-role"},
{"command":"chat group share-invite","emitted":"conversation-id","expect":"did-you-mean:ambiguous","via":"guard:source-vs-target-role"},
{"command":"chat message send","emitted":"user-id","expect":"user","via":"concept:user_id"},
{"command":"chat message list","emitted":"user-id","expect":"user","via":"concept:user_id"},
{"command":"chat +messages-list-direct","emitted":"user-id","expect":"user","via":"concept:user_id"},
{"command":"attendance +check-result","emitted":"user-ids","expect":"users","via":"concept:user_ids"},
{"command":"contact +list-sub-depts","emitted":"parent-id","expect":"dept","via":"concept:dept_id"},
{"command":"chat +conversation-info","emitted":"user-id","expect":"did-you-mean:blocked","via":"guard:user-id-vs-open-dingtalk-id"},
{"command":"chat group members remove","emitted":"user-ids","expect":"users","via":"concept:user_ids"},
{"command":"chat group members remove","emitted":"user-id","expect":"did-you-mean:blocked","via":"guard:single-vs-list"},
{"command":"chat group create","emitted":"user-id","expect":"did-you-mean:blocked","via":"guard:single-vs-list"},
{"command":"chat +chat-set-admin","emitted":"user-id","expect":"did-you-mean:blocked","via":"guard:single-vs-list"},
{"command":"chat +messages-read-status","emitted":"user-id","expect":"did-you-mean:blocked","via":"guard:single-vs-list"},
{"command":"chat group members list-by-ids","emitted":"open-dingtalk-ids","expect":"users","via":"concept:open_dingtalk_ids+bind"},
{"command":"chat group members remove-bot","emitted":"open-bot-id","expect":"bot-id","via":"concept:open_bot_id"},
{"command":"chat group members remove-bot","emitted":"robot-code","expect":"did-you-mean:blocked","via":"guard:robot-code-vs-open-bot-id"},
{"command":"chat group members add-bot","emitted":"robot","expect":"robot-code","via":"concept:robot_code"},
{"command":"chat +bot-find","emitted":"name","expect":"query","via":"override:scoped"},
{"command":"chat bot find","emitted":"name","expect":"query","via":"override:scoped"},
{"command":"chat +bot-search","emitted":"query","expect":"name","via":"override:scoped"},
{"command":"chat bot search","emitted":"query","expect":"name","via":"override:scoped"},
{"command":"chat message list-favorites","emitted":"limit","expect":"size","via":"override:scoped"},
{"command":"chat bot search","emitted":"current-page","expect":"page","via":"override:scoped"},
{"command":"chat bot search","emitted":"cursor","expect":"did-you-mean:blocked","via":"guard:page-number-vs-cursor"},
{"command":"chat message list-unread-conversations","emitted":"limit","expect":"count","via":"override:scoped"},
{"command":"chat +messages-list-unread-conversations","emitted":"size","expect":"count","via":"override:scoped"},
{"command":"chat message list","emitted":"start","expect":"time","via":"override:scoped"},
{"command":"chat message list","emitted":"end","expect":"did-you-mean:blocked","via":"guard:single-time-vs-range"},
{"command":"chat message list-all","emitted":"time","expect":"did-you-mean:blocked","via":"guard:time-range-required"},
{"command":"chat message list-by-sender","emitted":"user-id","expect":"sender-user-id","via":"override:scoped-role"},
{"command":"chat message list-by-sender","emitted":"open-dingtalk-id","expect":"sender-open-dingtalk-id","via":"override:scoped-role"},
{"command":"chat category create-smart","emitted":"title","expect":"name","via":"override:scoped"},
{"command":"chat category create","emitted":"name","expect":"title","via":"override:scoped"},
{"command":"chat message send","emitted":"file","expect":"file-path","via":"override:scoped"},
{"command":"chat category add-conv","emitted":"category-id","expect":"did-you-mean:blocked","via":"override:block-cardinality"},
{"command":"chat category rename","emitted":"category-ids","expect":"did-you-mean:blocked","via":"override:block-cardinality"},
{"command":"chat group-role set-user","emitted":"role-id","expect":"did-you-mean:blocked","via":"override:block-cardinality"},
{"command":"chat group-role update","emitted":"role-ids","expect":"did-you-mean:blocked","via":"override:block-cardinality"},
{"command":"chat message send-by-webhook","emitted":"at-user-ids","expect":"at-users","via":"override:scoped-role"},
{"command":"chat message send-by-bot","emitted":"at-users","expect":"at-user-ids","via":"override:scoped-role"},
{"command":"chat message send-by-bot","emitted":"at-ids","expect":"did-you-mean:ambiguous","via":"guard:user-id-vs-open-dingtalk-id"},
{"command":"chat +bot-search","emitted":"current-page","expect":"page","via":"override:scoped"},
{"command":"chat +category-create","emitted":"name","expect":"title","via":"override:scoped"},
{"command":"chat +category-rename","emitted":"name","expect":"title","via":"override:scoped"},
{"command":"chat +messages-list-direct","emitted":"start","expect":"time","via":"override:scoped"},
{"command":"chat +messages-list-unread-conversations","emitted":"limit","expect":"count","via":"override:scoped"},
{"command":"chat +messages-send-by-webhook","emitted":"at-user-ids","expect":"at-users","via":"override:scoped-role"},
{"command":"chat +unread-chats","emitted":"limit","expect":"count","via":"override:scoped"},
{"command":"chat +unread-chats","emitted":"size","expect":"count","via":"override:scoped"},
{"command":"chat category rename","emitted":"name","expect":"title","via":"override:scoped"},
{"command":"chat message list-unread-conversations","emitted":"size","expect":"count","via":"override:scoped"},
{"command":"doc +comment-create","emitted":"node-id","expect":"node","via":"concept:doc_node_id"},
{"command":"doc +doc-append","emitted":"node","expect":"doc","via":"concept:doc_node_id"},
{"command":"doc +find-doc","emitted":"keyword","expect":"query","via":"concept:search_query"},
{"command":"doc +search","emitted":"q","expect":"query","via":"concept:search_query"},
{"command":"doc +comment-list","emitted":"max-results","expect":"limit","via":"concept:pagination_size"},
{"command":"doc +list","emitted":"page-token","expect":"cursor","via":"concept:page_cursor"},
{"command":"doc +copy","emitted":"workspace-id","expect":"workspace","via":"concept:workspace_id"},
{"command":"doc +copy","emitted":"parent-folder-id","expect":"folder","via":"override:scoped-doc-folder"},
{"command":"doc +copy","emitted":"parent-id","expect":"did-you-mean:blocked","via":"guard:doc-folder-value-domain"},
{"command":"doc +comment-reply","emitted":"comment-id","expect":"comment-key","via":"concept:doc_comment_key"},
{"command":"doc comment reply","emitted":"mentioned-open-conversation-ids","expect":"mentioned-open-conversation-id","via":"override:scoped-role-list"},
{"command":"doc comment reply","emitted":"group-id","expect":"did-you-mean:blocked","via":"guard:numeric-group-id-vs-open-conversation-id"},
{"command":"doc +comment-create","emitted":"mentioned-open-conversation-id","expect":"did-you-mean:blocked","via":"guard:shortcut-missing-capability"},
{"command":"doc block insert","emitted":"parent-block-id","expect":"parent-block","via":"override:scoped-block-role"},
{"command":"doc block insert","emitted":"block-id","expect":"did-you-mean:ambiguous","via":"guard:parent-vs-reference-block-role"},
{"command":"doc media insert","emitted":"before-block-id","expect":"did-you-mean:blocked","via":"guard:requires-ref-block-plus-where"},
{"command":"doc read","emitted":"block-id","expect":"did-you-mean:ambiguous","via":"guard:requires-scope-and-boundary-role"},
{"command":"doc export get","emitted":"node","expect":"did-you-mean:blocked","via":"guard:document-node-vs-export-job"},
{"command":"doc import get","emitted":"job-id","expect":"did-you-mean:blocked","via":"guard:export-job-vs-import-task"},
{"command":"doc +version-revert","emitted":"version-number","expect":"version","via":"concept:doc_version_number"},
{"command":"doc +version-revert","emitted":"revision","expect":"did-you-mean:blocked","via":"concept:doc_version_number+exclude"},
{"command":"doc update","emitted":"version","expect":"did-you-mean:blocked","via":"guard:historical-version-vs-edit-revision"},
{"command":"doc +share-doc","emitted":"node-id","expect":"did-you-mean:blocked","via":"guard:node-id-needs-url-conversion"},
{"command":"doc +comment-create","emitted":"body","expect":"content","via":"concept:content_text"},
{"command":"doc +doc-append","emitted":"content","expect":"text","via":"concept:content_text"},
{"command":"doc +export-submit","emitted":"doc-id","expect":"node","via":"concept:doc_node_id"},
{"command":"doc +move","emitted":"parent-folder-id","expect":"folder","via":"override:scoped-doc-folder"},
{"command":"doc +template-list","emitted":"next-token","expect":"cursor","via":"concept:page_cursor"},
{"command":"doc +version-list","emitted":"page-size","expect":"limit","via":"concept:pagination_size"},
{"command":"doc +version-save","emitted":"file-id","expect":"node","via":"concept:doc_node_id"},
{"command":"doc comment create","emitted":"text","expect":"content","via":"concept:content_text"},
{"command":"doc comment create-inline","emitted":"body","expect":"content","via":"concept:content_text"},
{"command":"doc comment delete","emitted":"comment-id","expect":"comment-key","via":"concept:doc_comment_key"},
{"command":"doc comment update","emitted":"comment-id","expect":"comment-key","via":"concept:doc_comment_key"},
{"command":"doc version revert","emitted":"version-no","expect":"version","via":"concept:doc_version_number"},
{"command":"chat +chat-messages","emitted":"chat","expect":"group","via":"override:scoped-eval"},
{"command":"chat +search-msg","emitted":"chat","expect":"group","via":"override:scoped-eval"},
{"command":"chat +chat-update","emitted":"chat-id","expect":"group","via":"override:scoped-explicit-cid"},
{"command":"chat +chat-update","emitted":"conversation-id","expect":"group","via":"override:scoped-explicit-cid"},
{"command":"chat +chat-update","emitted":"open-conversation-id","expect":"group","via":"override:scoped-explicit-cid"},
{"command":"chat +chat-update","emitted":"id","expect":"did-you-mean:blocked","via":"guard:generic-id-value-domain"},
{"command":"chat +chat-update","emitted":"title","expect":"name","via":"override:scoped-group-title"},
{"command":"chat +chat-update","emitted":"new-title","expect":"name","via":"override:scoped-group-title"},
{"command":"chat +flag-list","emitted":"limit","expect":"page-size","via":"override:scoped-page-bound"},
{"command":"chat +flag-list","emitted":"max","expect":"did-you-mean:blocked","via":"guard:page-size-vs-total-count"},
{"command":"chat +flag-list","emitted":"max-results","expect":"did-you-mean:blocked","via":"guard:page-size-vs-total-count"},
{"command":"chat +flag-list","emitted":"max-size","expect":"did-you-mean:blocked","via":"guard:page-size-vs-total-count"},
{"command":"chat +chat-members-list","emitted":"chat-id","expect":"conversation-id","via":"override:scoped-explicit-cid"},
{"command":"chat +chat-members-list","emitted":"id","expect":"conversation-id","via":"override:scoped-explicit-cid"},
{"command":"chat +chat-members-list","emitted":"query","expect":"did-you-mean:blocked","via":"guard:unsupported-member-filter"},
{"command":"chat +conversation-set-top","emitted":"open-conversation-id","expect":"conversation-id","via":"override:scoped-explicit-cid"},
{"command":"chat +conversation-set-top","emitted":"chat-ids","expect":"conversation-ids","via":"override:scoped-explicit-cid-list"},
{"command":"chat +conversation-set-top","emitted":"groups","expect":"did-you-mean:blocked","via":"guard:group-name-or-list-ambiguity"},
{"command":"chat +conversation-set-top","emitted":"top","expect":"did-you-mean:blocked","via":"guard:inverse-boolean-semantics"},
{"command":"chat +chat-members-get","emitted":"conversation-id","expect":"id","via":"concept:open_conversation_id+bind"},
{"command":"chat +chat-members-get","emitted":"open-dingtalk-ids","expect":"users","via":"concept:open_dingtalk_ids+bind"},
{"command":"chat +chat-members-get","emitted":"group","expect":"did-you-mean:blocked","via":"guard:group-name-vs-open-conversation-id"},
{"command":"chat +chat-members-get","emitted":"chat","expect":"id","via":"concept:open_conversation_id+bind"},
{"command":"chat +chat-get-by-id","emitted":"group","expect":"did-you-mean:blocked","via":"guard:open-conversation-id-vs-numeric-group-id"},
{"command":"chat +messages-list","emitted":"start","expect":"time","via":"override:scoped-time-boundary"},
{"command":"chat +messages-list","emitted":"count","expect":"did-you-mean:blocked","via":"guard:page-size-vs-total-count"},
{"command":"chat +messages-list","emitted":"max-results","expect":"did-you-mean:blocked","via":"guard:page-size-vs-total-count"},
{"command":"chat +messages-list","emitted":"page-all","expect":"did-you-mean:blocked","via":"guard:requires-pagination-loop"},
{"command":"chat +messages-reply","emitted":"msg-id","expect":"ref-msg-id","via":"override:scoped-reference-message"},
{"command":"chat +messages-reply","emitted":"group","expect":"did-you-mean:blocked","via":"guard:group-name-vs-open-conversation-id"},
{"command":"chat +messages-reply","emitted":"chat","expect":"conversation-id","via":"concept:open_conversation_id"},
{"command":"chat +flag-cancel","emitted":"group","expect":"conversation-id","via":"concept:open_conversation_id"},
{"command":"chat +flag-cancel","emitted":"chat","expect":"conversation-id","via":"concept:open_conversation_id"},
{"command":"chat +flag-create","emitted":"group","expect":"conversation-id","via":"concept:open_conversation_id"},
{"command":"chat +chat-add-bot","emitted":"conversation-id","expect":"id","via":"concept:open_conversation_id+bind"},
{"command":"chat +chat-add-bot","emitted":"robot","expect":"robot-code","via":"concept:robot_code"},
{"command":"chat +chat-audit-join","emitted":"applicant-user-id","expect":"applicant","via":"override:scoped-user-role"},
{"command":"chat +chat-audit-join","emitted":"user-id","expect":"did-you-mean:ambiguous","via":"guard:applicant-vs-inviter-role"},
{"command":"chat +chat-create","emitted":"user-id","expect":"did-you-mean:blocked","via":"guard:single-vs-list"},
{"command":"chat +chat-mute-member","emitted":"user-ids","expect":"users","via":"override:scoped-mixed-id-list"},
{"command":"chat +chat-mute-member","emitted":"user-id","expect":"did-you-mean:blocked","via":"guard:single-vs-list"},
{"command":"chat +chat-remove-bot","emitted":"open-bot-id","expect":"bot-id","via":"concept:open_bot_id"},
{"command":"chat +chat-role-remove","emitted":"role-ids","expect":"did-you-mean:blocked","via":"guard:list-vs-single"},
{"command":"chat +chat-role-remove-user","emitted":"open-dingtalk-id","expect":"user","via":"override:scoped-mixed-id"},
{"command":"chat +chat-transfer-owner","emitted":"user-id","expect":"new-owner","via":"override:scoped-owner-role"},
{"command":"chat +feed-group-query-item","emitted":"chat-ids","expect":"conversation-ids","via":"override:scoped-explicit-cid-list"},
{"command":"chat +feed-group-query-item","emitted":"conversation-id","expect":"did-you-mean:blocked","via":"guard:single-vs-list"},
{"command":"chat +messages-batch-recall-by-bot","emitted":"message-id","expect":"did-you-mean:blocked","via":"guard:process-query-key-vs-open-message-id"},
{"command":"chat +messages-combine-forward","emitted":"src-open-cid","expect":"src-conversation-id","via":"override:scoped-source-role"},
{"command":"chat +messages-combine-forward","emitted":"conversation-id","expect":"did-you-mean:ambiguous","via":"guard:source-vs-destination-role"},
{"command":"chat +messages-forward","emitted":"source-message-id","expect":"msg-id","via":"override:scoped-source-role"},
{"command":"chat +messages-forward","emitted":"conversation-id","expect":"did-you-mean:ambiguous","via":"guard:source-vs-destination-role"},
{"command":"chat +messages-forward-topic","emitted":"src-open-message-id","expect":"src-msg-id","via":"override:scoped-source-role"},
{"command":"chat +messages-forward-topic","emitted":"message-id","expect":"did-you-mean:blocked","via":"guard:message-source-role"},
{"command":"chat +messages-recall-by-bot","emitted":"message-id","expect":"did-you-mean:blocked","via":"guard:process-query-key-vs-open-message-id"},
{"command":"chat +messages-resource-download","emitted":"conversation-id","expect":"open-conversation-id","via":"concept:open_conversation_id"},
{"command":"chat +messages-resource-download","emitted":"download-dir","expect":"did-you-mean:blocked","via":"guard:output-file-or-directory-contract"},
{"command":"chat +messages-set-pin","emitted":"conversation-id","expect":"open-conversation-id","via":"concept:open_conversation_id"},
{"command":"doc +create","emitted":"content-format","expect":"doc-format","via":"concept:doc_content_format"},
{"command":"doc +create","emitted":"content-file","expect":"did-you-mean:blocked","via":"guard:requires-file-read-transform"},
{"command":"doc +inspect","emitted":"include-versions","expect":"include-history","via":"override:scoped-section"},
{"command":"doc +inspect","emitted":"include","expect":"did-you-mean:blocked","via":"guard:requires-value-dependent-flag-expansion"},
{"command":"doc +inspect","emitted":"include-info","expect":"did-you-mean:blocked","via":"guard:base-info-always-returned"},
{"command":"doc +update","emitted":"mode","expect":"command","via":"override:scoped-operation"},
{"command":"doc +update","emitted":"revision","expect":"expected-revision","via":"concept:doc_edit_revision"},
{"command":"doc +update","emitted":"version","expect":"did-you-mean:blocked","via":"guard:historical-version-vs-edit-revision"},
{"command":"doc +fetch","emitted":"start-block","expect":"start-block-id","via":"override:scoped-boundary-role"},
{"command":"doc +fetch","emitted":"block-id","expect":"did-you-mean:ambiguous","via":"guard:start-vs-end-boundary-role"},
{"command":"doc +access-grant","emitted":"doc-id","expect":"node","via":"concept:doc_node_id"},
{"command":"doc +history-revert","emitted":"version-number","expect":"version","via":"concept:doc_version_number"},
{"command":"doc +create-from-template","emitted":"keyword","expect":"query","via":"concept:search_query"},
{"command":"doc +create-from-template","emitted":"workspace-id","expect":"workspace","via":"concept:workspace_id"},
{"command":"doc +create-from-template","emitted":"parent-folder-id","expect":"folder","via":"override:scoped-doc-folder"},
{"command":"doc +media-download","emitted":"file-id","expect":"did-you-mean:ambiguous","via":"guard:document-node-vs-attachment-resource-role"},
{"command":"doc +resource-update","emitted":"url","expect":"did-you-mean:ambiguous","via":"guard:document-url-vs-image-url-role"},
{"command":"doc +share","emitted":"node-id","expect":"did-you-mean:blocked","via":"guard:node-id-needs-url-conversion"},
{"command":"doc +copy","emitted":"dentry-uuid","expect":"node","via":"concept:doc_node_id"},
{"command":"doc info","emitted":"dentry-id","expect":"did-you-mean:blocked","via":"guard:dentry-id-vs-dentry-uuid"},
{"command":"doc create","emitted":"knowledge-base-id","expect":"workspace","via":"concept:workspace_id"},
{"command":"doc create","emitted":"space-id","expect":"did-you-mean:blocked","via":"guard:storage-space-vs-workspace"},
{"command":"drive list","emitted":"knowledge-base-id","expect":"workspace","via":"concept:workspace_id"},
{"command":"doc +search","emitted":"created-after","expect":"created-from","via":"concept:created_time_start"},
{"command":"doc +search","emitted":"creator-user-ids","expect":"creator-uids","via":"concept:creator_user_ids"},
{"command":"doc +access-grant","emitted":"permission-role","expect":"role","via":"concept:document_permission_role"},
{"command":"doc +export","emitted":"output-path","expect":"output","via":"concept:local_output_path"},
{"command":"drive download","emitted":"destination-path","expect":"output","via":"concept:local_output_path"}
]
}
}
@@ -0,0 +1,311 @@
{
"$schema": "./command_path_fallbacks.schema.json",
"version": 1,
"entries": [
{
"from": "chat +group-search",
"mode": "rewrite",
"to": "chat +chat-search",
"reviewed": true,
"review_reason": "0803 evaluation badcase: the model emitted +group-search with --query; +chat-search provides the same group-name search operation."
},
{
"from": "chat +members",
"mode": "rewrite",
"to": "chat +group-members",
"reviewed": true,
"review_reason": "20260728 evaluation badcases emitted +members three times for listing members of a group selected by name; +group-members is the unique reviewed read-only shortcut for that intent."
},
{
"from": "chat +group-member-list",
"mode": "rewrite",
"to": "chat +group-members",
"reviewed": true,
"review_reason": "20260728 evaluation emitted +group-member-list for a group-name member lookup; +group-members is the unique reviewed read-only shortcut and canonical parameter validation remains authoritative."
},
{
"from": "chat +list-group-bots",
"mode": "rewrite",
"to": "chat +chat-bots",
"reviewed": true,
"review_reason": "20260728 evaluation emitted +list-group-bots for listing robots in one group; +chat-bots is the unique reviewed read-only shortcut. The fallback preserves flags and does not reinterpret generic IDs."
},
{
"from": "chat +list-robot",
"mode": "rewrite",
"to": "chat +chat-bots",
"reviewed": true,
"review_reason": "20260728 evaluation emitted singular +list-robot for listing robots in one group; +chat-bots is the unique reviewed read-only shortcut. The fallback preserves flags and does not reinterpret generic IDs."
},
{
"from": "chat +list-robots",
"mode": "rewrite",
"to": "chat +chat-bots",
"reviewed": true,
"review_reason": "20260728 evaluation emitted +list-robots for listing robots in one group; +chat-bots is the unique reviewed read-only shortcut. The fallback preserves flags and does not reinterpret generic IDs."
},
{
"from": "chat +message-list",
"mode": "ambiguous",
"candidates": [
"chat +chat-messages",
"chat +messages-list-direct",
"chat +search-msg",
"chat +unread-chats"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +message-list without identifying group versus direct history, conversation history versus cross-chat search, or ordinary versus unread conversations; no candidate may be selected automatically."
},
{
"from": "chat +read-single",
"mode": "ambiguous",
"candidates": [
"chat +messages-list-direct",
"chat +chat-messages"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +read-single six times for direct-message history, but the invented name does not choose between the focused direct-history shortcut and the broader group/direct history workflow; command recovery must stop before parameter validation or dispatch."
},
{
"from": "chat +rename-group",
"mode": "rewrite",
"to": "chat +chat-update",
"reviewed": true,
"review_reason": "20260728 evaluation emitted +rename-group for the unique group-name update intent; +chat-update is the reviewed shortcut with that exact command-level operation. Parameter compatibility remains the canonical target's responsibility."
},
{
"from": "chat +send",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat +dm",
"chat +send-to-group"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send without a stable recipient type or sending identity; the write operation must stop until the caller chooses the unified, resolved-direct, or resolved-group workflow."
},
{
"from": "chat +send-message",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat +dm",
"chat +send-to-group"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-message without a stable recipient type or sending identity; the write operation must stop until the caller chooses the unified, resolved-direct, or resolved-group workflow."
},
{
"from": "chat +send-text",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat +dm",
"chat +send-to-group"
],
"reviewed": true,
"review_reason": "20260728 evaluation identified text content but not a stable recipient type or sending identity; the write operation must stop until the caller chooses the unified, resolved-direct, or resolved-group workflow."
},
{
"from": "chat +send-to",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat +dm",
"chat +send-to-group"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-to without proving whether the recipient denotes a user, group, or low-level identifier; the write operation must not choose a target workflow automatically."
},
{
"from": "chat +send-dm",
"mode": "ambiguous",
"candidates": [
"chat +dm",
"chat +messages-send"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-dm twice, but the invented name does not choose between the resolved-name text shortcut and the unified identifier-aware sending workflow; the write operation must not dispatch automatically."
},
{
"from": "chat +send-single",
"mode": "ambiguous",
"candidates": [
"chat +dm",
"chat +messages-send"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-single for a direct message, but the invented name does not choose between the resolved-name text shortcut and the unified identifier-aware sending workflow; the write operation must not dispatch automatically."
},
{
"from": "chat +send-by-bot",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat message send-by-bot"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-by-bot three times without a complete sending-identity contract; stop and present the unified identity-aware shortcut and the exact native bot sender instead of selecting a write path."
},
{
"from": "chat +group-send-text",
"mode": "ambiguous",
"candidates": [
"chat +send-to-group",
"chat +messages-send"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +group-send-text for a group text operation, but the invented name does not choose between name-resolved group text and the unified identifier-aware workflow; the write operation must not dispatch automatically."
},
{
"from": "chat +send-file",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat message send"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-file fifteen times with incompatible target and file parameter spellings; stop before dispatch and let the caller choose the unified shortcut or native current-user file workflow."
},
{
"from": "chat +send-image",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat message send"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-image without proving whether the input is an existing mediaId or a local file; stop before dispatch and let the caller choose the reviewed unified or native workflow."
},
{
"from": "chat +send-media",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat message send"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-media without a concrete media type, sending identity, or compatible parameter contract; stop before dispatch and let the caller choose the reviewed unified or native workflow."
},
{
"from": "oa +list-processes",
"mode": "ambiguous",
"candidates": [
"oa +list-forms",
"oa +my-initiated",
"oa approval list-initiated"
],
"reviewed": true,
"review_reason": "20260720 merged evaluation emitted +list-processes, but process can mean approval forms/templates or approval instances initiated by the current user; stop and present both shortcut workflows plus the exact native instance leaf."
},
{
"from": "chat +conversation-detail",
"mode": "rewrite",
"to": "chat +conversation-info",
"reviewed": true,
"review_reason": "20260804 multi-im badcase requested one conversation's details. +conversation-info is the unique current read-only shortcut for that operation. The rewrite changes only the command path and preserves every flag/value for target validation."
},
{
"from": "chat +bot-list",
"mode": "rewrite",
"to": "chat +chat-bots",
"reviewed": true,
"review_reason": "20260804 multi-im badcase requested the robot list for one group. +chat-bots is the unique current read-only shortcut. The fallback must not reinterpret the accompanying group flag."
},
{
"from": "chat +conversation-category-list",
"mode": "ambiguous",
"candidates": [
"chat +category-list",
"chat +category-list-conversations"
],
"reviewed": true,
"review_reason": "The invented name can mean listing the user's categories or listing conversations inside one category. No candidate may execute before the caller chooses the intended object level."
},
{
"from": "chat +conversation-group-list",
"mode": "ambiguous",
"candidates": [
"chat +category-list-conversations",
"chat +conversation-list"
],
"reviewed": true,
"review_reason": "The invented name can mean conversations in a custom category or the general conversation list. The command name alone does not identify the requested collection."
},
{
"from": "chat +list-my-groups",
"mode": "ambiguous",
"candidates": [
"chat +my-groups",
"chat +chat-list-mine",
"chat +chat-list"
],
"reviewed": true,
"review_reason": "The invented name does not choose between the established resolver shortcut, the legacy personal-group list and the current Schema-complete chat list. Recovery must stop instead of silently changing pagination or output semantics."
},
{
"from": "doc +list-templates",
"mode": "rewrite",
"to": "doc +template-list",
"reviewed": true,
"review_reason": "202608 Doc experiments emitted +list-templates while discovering available templates. +template-list is the unique current public read-only shortcut for that operation; the rewrite changes only the command path and preserves flags for target validation."
},
{
"from": "doc +search-template",
"mode": "rewrite",
"to": "doc +template-search",
"reviewed": true,
"review_reason": "202608 Doc experiments emitted the verb/object inversion +search-template. +template-search is the unique current public read-only template-name search shortcut; the rewrite preserves every flag and value."
},
{
"from": "doc +template",
"mode": "ambiguous",
"candidates": [
"doc +template-list",
"doc +template-search",
"doc +create-from-template"
],
"reviewed": true,
"review_reason": "202608 Doc experiments emitted the family-like +template without selecting browse, search, or create-from-template. Command recovery must stop before parameters or execution and present the three public workflows."
},
{
"from": "doc +version",
"mode": "ambiguous",
"candidates": [
"doc +history-list",
"doc +history-save",
"doc +history-revert"
],
"reviewed": true,
"review_reason": "202608 Doc experiments emitted the family-like +version without choosing list, save, or revert. The write and high-risk revert operations cannot be selected from an umbrella name; present the canonical history shortcuts and stop."
},
{
"from": "doc +create-version",
"mode": "rewrite",
"to": "doc +history-save",
"reviewed": true,
"review_reason": "202608 supplemental Doc badcase emitted +create-version while explicitly trying to save the current document as a recoverable history snapshot. +history-save is the unique current canonical shortcut for that operation and accepts the same --node value without parameter transformation."
},
{
"from": "doc +save-version",
"mode": "rewrite",
"to": "doc +history-save",
"reviewed": true,
"review_reason": "202608 supplemental Doc badcase emitted the verb/object inversion +save-version for a current-version snapshot. +history-save is the unique current canonical shortcut with the same document-node contract; the legacy +version-save path is not selected as the fallback target."
},
{
"from": "doc +snapshot",
"mode": "rewrite",
"to": "doc +history-save",
"reviewed": true,
"review_reason": "202608 supplemental Doc badcase emitted +snapshot during an explicit save-current-version workflow. The current +history-save selection contract names this exact recoverable history-snapshot operation, so the reviewed rewrite preserves the --node value and does not infer an update or export workflow."
},
{
"from": "doc +version-create",
"mode": "rewrite",
"to": "doc +history-save",
"reviewed": true,
"review_reason": "202608 supplemental Doc badcase emitted +version-create while saving the current document version. +history-save is the unique current canonical snapshot shortcut and the path-only rewrite leaves its real execution and safety contract authoritative."
}
]
}
@@ -0,0 +1,213 @@
# DWS Doc 产品参数问题与参数幻觉现状分析
> 分析日期:2026-08-03
> 代码基线:`main@187787040b0e1339cb7fb3fe3b1229f3746efce2`
> 分析范围:`dws doc` 产品;不把 `drive`、`wiki`、`markdown`、`devdoc` 的命令纳入数量统计,仅在 Doc 的产品边界和参数来源发生交叉时说明。
> 数据边界:仅使用当前代码、真实 Cobra/`--help`、内嵌 Schema 和 Doc Skill;未使用 `dws-eval`、历史 badcase 或实验扫描结果。
## 结论摘要
本轮共盘点 **65 个可执行业务入口**:63 个可执行叶子命令,以及同时可执行又包含 `get` 子命令的 `doc export`、`doc import` 两个父命令。其中 **49 个可从正常帮助路径发现,16 个属于隐藏或迁移兼容入口**。这些命令合计有 **250 个公开参数位、72 个不同的公开参数名**;代码还注册了 **279 个隐藏兼容参数位、31 个不同的隐藏兼容名称**。这里的“参数位”按“命令 × 参数”计数,同一个 `--node` 出现在 40 个命令中会计为 40 个参数位。
整体判断不是“Doc 参数完全失控”,而是已经存在一套较强的原生兼容能力,但治理分布不均:稳定命令通常通过隐藏 flag 接受 `--doc-id`、`--node-id`、`--file-id`、`--parent-folder-id`、`--workspace-id`、`--page-size` 等常见名称;`+shortcut` 大多没有复用这些兼容参数。当前中央 `param_concepts.json` 在 Doc 上只覆盖 **5 个命令、8 个 alias 映射和 11 个保护性名称**,因此模型在稳定命令与快捷命令之间迁移参数名时,仍会出现“同一业务含义,在一个入口可用、另一个入口 unknown flag”的情况。
Schema 的基础参数契约本身较健康:当前 60 个 Schema 工具与对应 Cobra 命令的公开参数名、参数类型逐项比较,**结构差异为 0**。风险主要集中在 Schema 合同之外或更高层语义上:
1. `--node` 是 Doc 的主标识,但 10 个 `+shortcut` 没有稳定命令已有的节点别名;`doc +doc-append` 又单独使用 `--doc`。
2. `--folder`、`--workspace`、`--limit`、`--cursor`、`--query` 在稳定命令上已有部分原生兜底,4~7 个同类快捷入口仍未覆盖。
3. 33 个稳定命令把隐藏的通用 `--id` 固定解释为文档节点;其中 19 个命令同时还有块、评论、附件、用户或位置等其他角色参数。对 `block insert`、`media insert` 一类写命令,错误的 `--id` 可能被忽略为节点别名,无法表达模型原本想指定的插入锚点。
4. `doc export` 的隐藏本地 `--format` 与全局输出 `--format` 同名并发生遮蔽;传 `doc export ... --format json` 并不能得到 JSON 输出,而会进入旧版导出格式兼容逻辑。
5. `doc export`、`doc import` 是真实可执行主入口,但不是叶子命令,当前 Schema 和中央 alias 生成器都不能把它们作为一个完整工具/命令范围处理。
6. Skill 对 17 个公开 `+shortcut` 没有参数段,对 style/template/version 共 11 个公开稳定命令也没有参数段;另外存在 `--user`/`--users`、`--max-results`/`--limit`、导出格式、`--parent-block`、角色大小写等事实漂移。
因此,第一轮标准化应优先做三件事:补齐低风险、只改参数名的中央 concept/override;给块定位、任务 ID 等高风险近义词增加 block/ambiguous;把真实 flag 冲突、非叶子 Schema、参数组合约束和 Skill 漂移留给命令代码或 Schema/Skill 修复,不用 alias 表强行解决。
## 一、分析口径与现状量化
| 指标 | 结果 | 说明 |
|---|---:|---|
| 可执行业务入口 | 65 | 63 个叶子命令 + `doc export` + `doc import` |
| 正常帮助路径可发现 | 49 | 不经过隐藏祖先命令即可发现 |
| 隐藏/迁移兼容入口 | 16 | 仍可执行,其中 13 个进入 Schema,3 个未进入 Schema |
| Schema 工具 | 60 | 当前 `dws schema doc` 的工具数 |
| Help/Schema 参数名和类型差异 | 0 | 对 60 个 Schema 工具逐项比较公开 flag |
| 公开参数位 | 250 | 命令 × 公开参数 |
| 不同公开参数名 | 72 | 去重后的 canonical flag 名 |
| 隐藏兼容参数位 | 279 | 命令 × hidden flag |
| 不同隐藏兼容名 | 31 | 去重后的 hidden flag 名 |
| 有原生 hidden alias 的业务入口 | 43 | 主要是稳定命令和迁移兼容命令 |
| Doc 中央 alias 覆盖 | 5 个命令 | 2 个 concept + 3 个 command override |
| Doc 中央 alias/保护输出 | 8 / 11 | 8 个映射、11 个 blocked 名称 |
| Doc 校验 fixture | 6 | 3 个 alias case + 3 个 guard case |
当前中央能力覆盖的 Doc 行为只有:
- `doc +template-search`:`keyword/keywords/q/search-word → query`;保护 `name/subject/text/title`。
- `doc block insert`:`body/content → text`;保护 `before-block-id/name/title`。
- `doc block update`:`body/content → text`;保护 `name/title`。
- `doc +export-get`:保护 `node`,避免把文档节点当导出任务 ID。
- `doc block delete`:保护 `index`,避免把块位置当块 ID。
## 二、集中参数问题
### 2.1 文档节点标识命名没有贯穿稳定命令与快捷入口
Doc 的业务 canonical 是 `--node`。43 个业务入口公开使用 `--node`,其中 33 个稳定/兼容入口原生接受 `--doc-id`、`--node-id`、`--file-id`,大部分还接受 `--id`、`--url`。以下 10 个快捷入口只有 `--node`,没有同等兜底:
`doc +comment-create`、`doc +comment-create-inline`、`doc +comment-list`、`doc +comment-reply`、`doc +copy`、`doc +export-submit`、`doc +move`、`doc +version-list`、`doc +version-revert`、`doc +version-save`。
`doc +doc-append` 进一步把同一文档目标命名为 `--doc`,这会让从 `doc update --node ... --content ...` 迁移来的调用同时猜错目标参数和内容参数。
可由 alias 表解决的部分:新增命令范围严格限定的 `doc_node_id` concept,在上述快捷入口中将 `doc-id/node-id/file-id/document-id/url` 归一到 `node`,并为 `doc +doc-append` 用 bind 将真实 `doc` 归入同一概念。不要把 `doc list` 纳入该 concept:它的隐藏 `--node` 实际表示“要列子节点的文件夹”,不是文档操作目标。
不能直接归一的边界:`doc +share-doc --url` 要求的是可点击文档链接,不能把只有 nodeId 的 `--node` 仅靠改名变成 URL。这里应 block `node/doc-id/node-id` 并提示先取得文档 URL,而不是做 alias。
### 2.2 目标文件夹和知识库参数在快捷入口上缺少同等兼容
- 13 个命令公开使用 `--folder`;9 个稳定命令已经原生接受 `parent-folder/parent-folder-id/parent-node-id/parent-id`,4 个快捷入口 `doc +copy/+list/+move/+template-apply` 没有。
- 17 个命令公开使用 `--workspace`;13 个稳定命令已经原生接受 `--workspace-id`,同样是上述 4 个快捷入口没有。
`workspace-id → workspace` 可以直接复用现有 `space_id` concept,值域仍是同一个知识库 ID/URL。文件夹建议建立独立的 `doc_folder_node_id`,因为 Doc 的 folder 值必须是文档文件夹 nodeId/dentryUuid/URL,不能复用当前表示 drive folder id 的 `folder_id` 概念。
需要额外约束:`parent-id` 在其他产品经常表示纯数字 dentryId。稳定命令会通过 `validateDocFolderID` 拒绝纯数字值,但快捷入口直接把值发给接口,没有同等校验。alias 表只看名称,无法保证值域;第一轮不应把 `parent-id` 广泛新增到快捷入口,除非先补齐相同的值校验。
### 2.3 分页和搜索命名是最适合直接复用已有 concept 的一组
- 14 个命令公开使用 `--limit`。7 个稳定命令已有 `--page-size` 或 `--max-results` 原生兼容;以下 7 个快捷入口没有:`doc +comment-list`、`doc +find-doc`、`doc +list`、`doc +search`、`doc +template-list`、`doc +template-search`、`doc +version-list`。
- 12 个命令公开使用 `--cursor`。6 个稳定命令已有 `--page-token/--next-token`;以下 6 个快捷入口没有:`doc +comment-list`、`doc +list`、`doc +search`、`doc +template-list`、`doc +template-search`、`doc +version-list`。
- 5 个命令公开使用 `--query`。稳定 `doc search`、`doc template search` 原生接受 `--keyword`,`doc +template-search` 由中央 concept 接受;`doc +find-doc`、`doc +search` 仍不接受 `--keyword`。
这组参数都保持原值、类型和分页角色不变,可分别扩展现有 `pagination_size`、`page_cursor`、`search_query` 的 Doc 命令范围。`count/page/offset` 不应顺手加入:它们分别可能表示总数、页码或偏移量,现有 concept 的 excludes 应继续生效。
### 2.4 评论和权限参数存在“名称可兼容、参数形态不一致”两类情况
权限命令的真实 canonical 是 `--users`:`doc permission add/update/remove` 都接受逗号分隔 userId 列表。Skill 的权限子文档和部分意图说明仍教 `--user`;运行时因为原生 hidden alias 已接受 `user/user-id/user-ids/uid/user-list/user-id-list`,不会直接报 unknown flag,但会形成两个事实来源。这里应修改 Skill 统一教 `--users`,无需再向中央表重复添加。
评论命令统一公开 `--mention`,但稳定命令使用普通 string(要求 CSV),快捷命令使用 stringSlice(支持重复或 CSV)。名称相同、出现次数语义却不同:同一参数重复传入时,稳定命令最后一个值覆盖,快捷命令会累积。中央 alias 只改名,无法把两种出现次数语义变成硬等价;在统一 Cobra 类型前,不建议把通用 `--user-ids` concept 直接绑定到 `--mention`。
`doc comment create/reply/update` 的 `--mentioned-open-conversation-id` 实际是 stringSlice,可重复或 CSV,flag 名却是单数。可以增加严格限定到这 3 个命令的 `mentioned_open_conversation_ids` concept,接受复数 `mentioned-open-conversation-ids` 以及同角色的 `open-conversation-id(s)`;必须 block `group-id/group-ids`,因为数字群号不是 openConversationId。
稳定评论的群 @能力没有出现在 `doc +comment-create` 和 `doc +comment-reply` 快捷入口中。alias 表不能凭空给快捷命令新增底层能力;要么补快捷命令实现,要么明确路由稳定命令。
### 2.5 块标识与位置参数必须保留角色,不能做“看起来像”的同义归一
Doc 同时存在:
- `--block-id`:要读取、修改、删除或锚定评论的目标块。
- `--ref-block`:插入操作的同级参考块,通常还要结合 `--where before/after`。
- `--parent-block`:容器内插入的父容器。
- `--start-block-id/--end-block-id`:局部读取的区间边界。
- `--index/start-index/end-index/start/end`:分别表示块位置或块内字符偏移,不能互换。
现有 `before-block-id` block 是正确的:它需要同时生成 `--ref-block <id>` 和 `--where before`,仅改参数名会默认为 after。应将相同保护补到 `doc media insert`;为 `parent-block-id → parent-block`、`reference-block-id → ref-block` 添加角色保持的 scoped alias;把 `doc block insert --block-id` 标记为 ambiguous,因为无法判断是 ref 还是 parent。`doc read --block-id` 也不能简单映射,它需要同时确定 `scope=section` 和 `start-block-id`。
### 2.6 评论、任务、模板和版本标识需要小范围 concept/guard
- `--comment-key`:出现在 `doc comment reply/update/delete` 和 `doc +comment-reply`。`comment-id → comment-key` 可以保持相同 opaque 值直接归一;通用 `--id` 不可以。
- `--job-id`:只用于导出任务查询;`--task-id`:只用于导入任务查询。`doc +export-get` 已 block `node`,但稳定 `doc export get` 未保护,且两条导出查询都还应保护 node 的其他拼写和 `task-id`;`doc import get` 应反向保护文档节点名和 `job-id`。
- `--template-id`:稳定 `doc template apply` 原生接受 `template/tpl-id`,隐藏快捷 `doc +template-apply` 没有,可补 scoped alias。
- `--version` 与 `doc update --revision` 不是同一字段。前者是历史版本号,后者是并发检查版本。建议互相 block,而不是互设 alias。
### 2.7 原生通用 `--id` 过宽,中央 alias 无法覆盖真实 flag
33 个命令把隐藏 `--id` 注册为 `--node` 的真实兼容 flag;19 个命令同时还有其他角色参数。由于 `--id` 已经是真实 Cobra flag,中央 alias/blocked/ambiguous 不会接管它。
高风险例子是 `doc block insert --node DOC --id BLOCK --text ...`:`--node` 已经提供后,`--id` 仍会被解析为另一个节点别名并在 fallback 中被忽略,`--ref-block` 为空,命令可能按默认位置插入,而不是在 BLOCK 附近插入。类似风险也存在于 `doc media insert`。评论 update/delete 多数会因为缺 `--comment-key` 而失败,风险较低,但错误提示仍会偏离模型意图。
这类问题需要收窄或删除多标识命令上的原生 `--id`,或让原生 alias 注册支持按命令保护;不能靠 `param_concepts.json` 覆盖一个已存在的真实 flag。
### 2.8 `doc export --format` 与全局输出参数发生真实名称冲突
`doc export` 为兼容旧版,在本地注册了 hidden `--format` 作为 `--export-format` 的别名。这会遮蔽根命令的全局 `--format`。实测:
```text
dws doc export --node demo --output /tmp/demo.docx --dry-run --format table
```
命令仍按 `docx` 导出格式执行预览,`table` 没有控制输出呈现;同理 `--format json` 也不能把预览切成 JSON。这个冲突发生在真实 flag 解析层,而且 `doc export` 又不是叶子命令,中央 alias 不能修复。建议移除旧本地 `--format`,只保留 `--export-format`,全局 `--format` 恢复统一含义。
### 2.9 参数组合约束仍有缺口
当前 Schema 只为 3 个 Doc 命令发布组合约束:`block insert/update` 的 `text|heading|element` 至少一个,以及 `doc update` 的 `content|content-file` 二选一。仍需注意:
- `block insert/update` 只声明“至少一个”,未声明三者互斥;运行时代码按 `element > heading > text` 静默选一个。
- `block insert` 的 `ref-block`、`parent-block`、`index` 和 `where` 角色关系没有完整 Schema 约束。
- `media insert --where` 只有与 `--ref-block` 同时出现才有意义,Schema 未表达。
- `comment reply --emoji` 与 `--mentioned-open-conversation-id` 运行时互斥,Schema 未表达。
- `doc create` 同时给 `content` 和 `content-file` 时运行时优先文件,但 Schema 未声明互斥。
这些都是参数组合问题,不是参数名问题;应补 Runtime Schema constraints 和对应运行时校验,不应通过 alias 伪装成单参数修复。
### 2.10 Schema 与 Skill 的参数覆盖边界
60 个 Schema 叶子工具与 Cobra 公开参数名/类型一致,这是本轮最重要的正向结论。但存在两个结构边界:
1. `doc export`、`doc import` 是可直接执行的主命令,同时又含 `get` 子命令,因此不属于当前 Schema 的叶子工具。`dws schema --cli-path "doc export"` 只返回 `doc export get`,不会展示主命令的 `node/output/export-format`;`doc import` 同理只返回 `import get`。
2. 当前中央 alias 生成器也要求命令范围匹配 runnable leaf,无法给这两个主入口新增中央 alias。
Skill 的参数信息存在以下事实漂移或覆盖不足:
- 17 个公开 `+shortcut` 没有独立参数段,只能依赖 `--help`/Schema。
- style 5 条、template 3 条、version 3 条共 11 个公开稳定命令没有参数段;隐藏兼容的 `permission remove` 也未覆盖。
- 权限子文档使用 `--user`,真实 canonical/Schema 是 `--users`。
- 权限列表子文档使用 `--max-results` 且写默认 50,真实 canonical 是 `--limit`,默认 30;`--max-results` 只是 hidden alias。
- Skill 声明 role 必须大写且大小写敏感,运行时代码实际会 trim 并转成大写。
- Skill 声明 export 仅支持 docx,当前 `--help` 已支持 docx/markdown/pdf。
- `block insert` 文档示例使用 `--parent-block`,但参数清单没有列出它。
- `doc-info.md` 声称 `--parent-id` 不是 Doc 参数,实际 9 个命令把它注册为 hidden folder alias;真正需要强调的是值必须是 Doc 文件夹 nodeId/URL,不能是纯数字 dentryId。
- `doc import --help` 文案说 folder/workspace 至少传一个,但运行时 `docImportFlowConfig.requireTarget=false`,两者都不传可以导入到默认位置;Skill 在这一点反而与运行时一致。
这些问题不能由别名表替代修复:Skill 应统一教 canonical 参数,Schema 应解决非叶子入口暴露,Help 应与真实运行约束一致。
## 三、建议的别名表实现
以下是第一轮建议,目标是只接受“同一业务实体、同一值域、同一类型、同一角色、值原样透传”的映射。
| 建议项 | 实现方式 | 主要命令范围 | 结论 |
|---|---|---|---|
| `doc_node_id` | 新 concept;`node/node-id/doc-id/file-id/document-id/url/doc`,按命令实际 canonical 归一 | 10 个缺兼容的 node 快捷入口 + `doc +doc-append` | 可实现;排除 `doc list`、`doc +share-doc`、任务查询命令 |
| `doc_folder_node_id` | 新 concept;优先 `folder/folder-id/parent-folder/parent-folder-id/parent-node-id` | `doc +copy/+list/+move/+template-apply` | 可实现;`parent-id` 先不扩散,避免数字 dentryId 值域问题 |
| `space_id` | 扩展现有 concept 的命令范围 | 上述 4 个快捷入口 | 可实现;`workspace-id → workspace` |
| `pagination_size` | 扩展现有 concept | 7 个缺兼容的 limit 快捷入口 | 可实现;继续排除 count/page/cursor |
| `page_cursor` | 扩展现有 concept | 6 个缺兼容的 cursor 快捷入口 | 可实现 |
| `search_query` | 扩展现有 concept | `doc +find-doc`、`doc +search`,并可补全稳定 search/template search 的 q 等拼写 | 可实现;保持 name/title/text 排除 |
| `comment_key` | 新 concept;`comment-key/comment-id` | comment reply/update/delete 与 `+comment-reply` | 可实现;通用 id 不纳入 |
| `mentioned_open_conversation_ids` | 新 concept;保留 mention 角色 | comment create/reply/update | 可实现;block group-id(s) |
| Block role alias | scoped alias | `parent-block-id → parent-block`、`reference-block-id → ref-block` | 可实现 |
| Block role guard | block/ambiguous | insert/media 的 before-block-id,insert 的 block-id,read 的 block-id | 必须保护,不能猜 |
| 任务 ID guard | command override | export get、import get | 可实现;job/task/node 分域 |
| Template/version | scoped alias + block | template/tpl-id;version-number;version vs revision | 可实现,优先级较低 |
| `doc +share-doc` | command override block | node/doc-id/node-id | 只提示需要 URL,不做映射 |
不建议把所有原生 hidden alias 再复制到中央表。稳定命令已经具备并经过代码读取的兼容 flag,中央表应优先补“快捷入口缺口”和“需要明确 guard 的角色冲突”,以免形成两套重复事实。
## 四、当前能力无法单独解决的事项
| 事项 | 为什么 alias 表解决不了 | 应修改的位置 |
|---|---|---|
| `doc export`/`doc import` 主入口缺 Schema/中央 alias | 两者是可执行父命令,不是 runnable leaf | Schema 命令模型/命令树结构,或提供真正的叶子主入口 |
| hidden `--id` 过宽 | 它已经是真实 Cobra flag,中央 alias 不会覆盖 | `RegisterCrossProductAliases` 或 Doc 命令注册策略 |
| export 本地 `--format` 冲突 | 与全局 flag 同名且已进入 pflag | 删除旧 alias,仅保留 `--export-format` |
| `before-block-id` 等一变二参数 | 需要同时生成 ref-block + where,超出名称归一 | 命令级转换器或保留 block 提示 |
| share-doc 的 nodeId 变 URL | 需要查询/构造链接,是值转换和业务调用 | shortcut 编排逻辑 |
| 快捷评论缺群 @ | 真实 Flag/Execute 都不存在该能力 | shortcut 参数和 Execute 实现 |
| comment mention string/stringSlice 不一致 | 重复出现次数语义不同,改名不能保证等价 | 统一 Cobra flag 类型和 payload 测试 |
| 参数组合约束不完整 | 涉及互斥、依赖、优先级,不是单个参数名 | Runtime Schema constraints + RunE 校验 |
| folder 数字值域在快捷入口未校验 | alias 不检查值内容 | 快捷入口复用 `validateDocFolderID` |
| Skill/Help 事实漂移 | 文档事实错误不会被运行时 alias 自动纠正 | Doc Skill、Cobra usage、Schema 暴露 |
## 五、验证结果
- 使用当前 `main` 重新构建 `./dws`,从 `app.NewSchemaSourceRootCommand()` 枚举 Doc 命令树,避免用户 shortcut/plugin 影响分析结果。
- 对 60 个 Schema 工具逐条比较对应 Cobra 命令的公开 flag 名称和类型,差异为 0。
- 确认 3 个可执行 hidden leaf 未进入 Schema:`doc +comment-create-inline`、`doc +template-apply`、`doc file search`;这是当前公开 Schema 边界的一部分。
- 确认 `doc export`、`doc import` 为可执行非叶子命令,Schema 路径查询只返回各自的 `get` 子命令。
- 读取并核对 `param_concepts.json`、生成后的 alias 表、Doc helpers、Doc shortcuts、Doc Skill 主文档及全部 13 个命令子文档。
- 通过 `doc export --dry-run` 复现本地 `--format` 对全局输出参数的遮蔽。
## 六、建议实施顺序
1. 先补 `search_query`、`pagination_size`、`page_cursor`、`space_id` 和缺失快捷入口的 node/folder alias,并为每个映射补 canonical payload 等价测试。
2. 同步补 `comment-key`、任务 ID、块角色的 block/ambiguous;这些保护比扩大通用同义词更重要。
3. 修复 Skill/Help 的 canonical 参数、默认值和枚举,补 style/template/version 与 shortcut 的参数索引。
4. 单独处理 native `--id`、export `--format`、非叶子 Schema 和组合约束;这些属于命令/Schema 设计,不要塞进 JSON 兜底。
@@ -0,0 +1,188 @@
# DWS Doc 参数幻觉静态审计与治理方案
> 分析日期:2026-08-06
> 审计基线:`fix/param-hallucination@b94e21331d7d3dafa08dca8764d695d5ae3317f2`
> 方法:遵循 `specs/product-cli-param-hallucination-analysis-spec.md`,以当前 Cobra/`--help` 为最高事实,其次为运行时组装 Schema、Doc Skill、当前参数兜底配置。
> 数据边界:本报告的静态结论不使用历史实验频次;`/Users/hyz/works/data/doc` 的逐 case 结果单列于 `doc_experiment_hallucination_observations_20260806.md`,只用于验证和补充候选项。
> 分支说明:本地 `origin/main@1fe01999` 比审计分支多两个发布元数据提交,但没有修改 Doc shortcut、Doc 命令声明或 Doc Schema;其相对差异中删除参数/命令兜底文件,是因为本 PR 尚未合入 main,不代表 Doc 命令面回退。
## 结论摘要
当前 Doc 命令面的公开契约总体健康:命令树共盘点 108 个 Doc 节点,稳定 Schema 发布 90 个工具;47 个 Doc shortcut 中 45 个公开并进入 Schema,2 个隐藏兼容 shortcut 未发布。对 90 个 Schema 工具逐项比较当前 Help 的本地公开 flag,参数名差异为 **0**。扫描 41 个 Doc Skill 文件中的 955 条 `dws doc` 代码片段后,也没有发现真实可执行示例使用当前命令不存在的 flag;扫描器初报的 15 条路径均为 `[flags]`、`create/update`、`version save/list/revert` 或省略号等文档记法,不是可执行命令。
风险不在“Schema 与 Help 大面积不一致”,而在同一业务实体跨稳定命令和 shortcut 使用不同参数名,以及部分相似名称需要值转换、角色判断或多参数展开。新增的 28 个 shortcut 扩大了这一表面:例如文档目标通常叫 `--node`,内容格式在 shortcut 中叫 `--doc-format`,历史版本与编辑 revision 又必须严格分域。
本轮候选 `param_concepts.json` 采用三类治理:
1. 对可原样透传的同实体、同角色参数增加严格命令范围的 concept 或命令级别名;
2. 对需要文件读取、URL 构造、值依赖展开或角色选择的输入,在执行前 block/ambiguous;
3. 保持真实 Cobra 参数、必填/互斥约束和安全确认不变,不用别名创造能力。
候选表已在隔离副本中完成生成器校验:生成 281 个命令的参数映射;相关 `internal/cli` 参数/命令兜底测试和 `internal/pipeline` 测试通过。候选仍只位于本目录,尚未同步到正式 `internal/cli` 文件。
## 一、审计范围和当前事实
| 指标 | 结果 | 说明 |
|---|---:|---|
| Doc 命令树节点 | 108 | 包含可执行父命令、稳定叶子、shortcut 和兼容入口,不含名称前缀误命中的 `doctor` |
| Schema 工具 | 90 | 当前运行时组装 `schema --all` 中 product=doc 的工具 |
| Doc shortcut | 47 | 45 个公开、2 个隐藏兼容 |
| 相比实验基线 `000bc134` 新增 shortcut | 28 | 旧快照 19 个、当前 47 个 |
| Help/Schema 参数名漂移 | 0 | 90 个 Schema 工具逐命令对账 |
| Doc Skill 文件/代码片段 | 41 / 955 | 未发现真实可执行片段的 flag 漂移 |
| 当前正式表中涉及 Doc 的 concept/override 命令 | 31 | 变更前覆盖,仍有新增 shortcut 缺口 |
| 候选 Doc concept / command override | 10 / 25 | 是完整候选表中的 Doc 相关配置,不是全局展开表 |
| 候选 Doc 验证 fixture | 57 | 包含既有和本轮新增正反例 |
45 个公开 shortcut 均已发布到 Schema。隐藏兼容项是:
- `doc +comment-create-inline`
- `doc +template-apply`
这两项仍可执行,但不能视为 Agent 正常选路面;候选参数治理不会把它们重新发布到 Schema。
## 二、聚合参数问题
### 2.1 文档节点标识在新增 shortcut 上缺少一致兜底
Doc 的主要文档目标是 `--node`,模型常生成 `--node-id`、`--doc-id`、`--file-id`、`--document-id`、`--doc` 或 `--url`。这些名称只有在目标参数确实接受同一个 nodeId/URL/token 且值可原样传递时才等价。
候选扩展 `doc_node_id` 到当前真实接受 `--node` 或特例 `--doc`、且不存在第二种媒体/资源身份的 Doc 命令,包括 access、background、checkpoint、comment、export、fetch、history、inspect、review 等新增 shortcut,以及对应稳定命令。媒体和资源 shortcut 采用更窄的命令级映射:只有 `doc/doc-id/document-id/node-id` 可归一到 `--node`;`file-id/url` 因可能表示附件、封面或图片 URL,必须按歧义停止。以下边界明确排除:
- `doc +share` 的真实目标是 `--url`,不能只把 nodeId 政名为 URL;应 block 并提示提供共享链接。
- `doc +media-*`、`doc +resource-*` 同时存在文档节点与媒体/资源角色,不能把通用 `--file-id/--url` 自动归一为文档 `--node`。
- 导入/导出任务的 `--job-id`、`--task-id`,评论 `--comment-key`、块 `--block-id`、历史 `--version` 和编辑 `--revision` 都不是文档节点。
- 通用 `--id` 角色不明确,继续排除。
结论:同值域、同角色部分可由 concept 解决;需要 URL 构造或角色判断的部分必须拦截。
### 2.2 workspace、folder、搜索和分页是低风险补齐项
新增 shortcut 广泛使用 `--workspace`、`--folder`、`--query`、`--limit`、`--cursor`,而模型会沿用稳定命令中的 `--workspace-id`、`--parent-folder-id`、`--keyword`、`--page-size`、`--page-token`。
候选方案:
- 扩展 `space_id` 到当前可用的 Doc create/import/access/list/move 等命令,保持同一个 workspace 值不变;
- 只在精确 Doc 命令上把 `folder-id/parent-folder/parent-folder-id/parent-node-id` 归一到 `--folder`;
- `parent-id` 继续 block,因为它可能携带数字 Drive dentryId,不能证明与 Doc folder nodeId 同值域;
- 扩展 `search_query`、`pagination_size`、`page_cursor` 到新增 shortcut 和仍公开的稳定命令;继续排除 page、offset、count 等不同分页语义。
结论:均可在现有别名表中实现,但 folder 必须采用命令级严格范围,不能建立跨产品全局映射。
### 2.3 内容、内容格式与文件输入不能混为一类
文本体的 `text/content/body` 可以在评论、checkpoint、create、append 等确实接收同一原始文本的命令中归一。`--content-format` 与 `--doc-format` 在 create/update 上同为 `markdown|jsonml`,也可通过新 concept `doc_content_format` 原样映射。
`--content-file` 不同:`doc +create`/`doc +update` 的 `--content` 支持 `@relative-path` 或 stdin,而模型传入的是裸文件路径。中央别名只改 flag 名,不会读取文件,也不会自动补 `@`。如果直接改成 `--content /tmp/a.md`,文件路径会被当成正文,产生静默错误。
结论:文本和格式可自动别名;`content-file` 在 shortcut 上必须 block,提示改用 `--content @relative-path`、stdin,或使用真实支持 `--content-file` 的稳定命令。
### 2.4 历史版本、编辑 revision、评论 key 必须分域
- `--version/--version-number/--version-no` 表示历史版本号,可在 history/version revert 范围归一;
- `--revision/--expected-revision` 表示乐观并发编辑修订号,只适用于 update;
- `--comment-key/--comment-id` 表示 commentKey,只适用于评论 reply/update/delete。
候选新增 `doc_edit_revision`,扩展 `doc_version_number` 和 `doc_comment_key`。历史 version 与 edit revision 互相 block;通用 `--id` 不进入评论 key concept。
结论:可做小范围 concept,但不能因名称接近而互相兜底。
### 2.5 fetch/inspect 的范围与 section 参数有角色语义
`doc +fetch` 的 `--start-block-id`、`--end-block-id` 分别是区间边界。`start-block/end-block` 可保持角色映射;角色不明的 `--block-id` 无法决定起点还是终点,必须 ambiguous。
`doc +inspect` 的基础文档信息始终返回:
- `--include-versions` 可精确映射为 `--include-history`;
- `--include-info` 是冗余幻想,block 并说明无需参数;
- `--include blocks` 需要读取值后决定是否改用 `+fetch`,不是单纯 flag 改名,必须 block。
结论:精确 section 同义词可映射;通用 include 和无角色 block-id 不可猜测。
### 2.6 update 操作选择可别名,但不能扩大取值集合
`doc +update --command append|overwrite` 与模型常写的 `--mode append|overwrite` 在该 shortcut 上含义一致,可做命令级 `mode → command`。该映射只改变参数名,最终仍由目标命令校验允许值,不会把其他 mode 变成合法操作。
结论:可实现,必须限制在 `doc +update`,并保留 Cobra 的必填、取值和安全确认。
### 2.7 access/share 的收件人、文档目标和共享 URL 是不同角色
access 系列的 `--to`、grant-and-share 的 `--node` 与 `--url`、share 的 `--url` 虽然都围绕“分享文档”,但值域和角色不同。候选只在 `doc +grant-and-share` 将明确文档 ID 拼写映射到 `--node`,原生 `--url` 继续表示发送给收件人的共享链接;不把 `to/user/member` 进行全局互换。
结论:只做精确文档目标别名;收件人解析和 node→URL 转换超出当前能力。
### 2.8 已存在的真实 flag 与参数组合问题不属于别名表
真实 Cobra flag 优先于中央别名。历史上过宽的 hidden `--id`、`doc export` 本地 `--format` 与全局输出格式冲突,以及 block/parent/ref、content/content-file 等组合约束,都不能通过 `param_concepts.json` 覆盖。
结论:需要修改命令声明、运行时校验或 Schema constraints;本轮候选不扩大这些行为。
## 三、候选参数表的具体动作
候选文件:`docs/parameter-hallucination/doc/param_concepts.json`。
| 治理对象 | 建议配置 | 处理方式 | 验证重点 |
|---|---|---|---|
| 文档 node 标识 | 扩展 `doc_node_id`,仅纳入值可原样传递且无第二资源身份的命令;media/resource 改为强身份命令级别名 | 增加概念别名/命令级别名 | 不进入 job/task/comment/block/version/revision 值域;media/resource 的 `file-id/url` 保持 ambiguous |
| workspace | 扩展 `space_id` | 增加概念别名 | `workspace-id` 与 `workspace` 值不变 |
| Doc folder | 精确命令 `folder-id/parent-folder-* → folder`;block `parent-id` | 命令级别名 + 安全拦截 | 数字 Drive dentryId 不被静默传入 |
| 搜索与分页 | 扩展 `search_query`、`pagination_size`、`page_cursor` | 增加概念别名 | 不把 page/offset/count 当 cursor/limit |
| 文本内容 | 扩展 `content_text` | 增加概念别名 | 只接受原始文本,不承担文件读取 |
| 内容格式 | 新增 `doc_content_format` | 增加概念别名 | `content-format → doc-format`,排除通用 format |
| 文件内容 | `+create/+update` block `content-file` | 安全拦截 | 明确提示 `@relative-path`/stdin/稳定命令 |
| 编辑 revision | 新增 `doc_edit_revision` | 增加概念别名 + version 隔离 | revision 与历史版本不互换 |
| inspect | `include-versions → include-history`;block include/include-info | 命令级别名 + 安全拦截 | 不做值依赖展开,不制造冗余 flag |
| fetch | start/end 精确别名;block-id ambiguous | 命令级别名 + 提示歧义 | 保留区间边界角色 |
| share | block 文档 ID 拼写 | 安全拦截 | 不把 nodeId 改名伪装成 URL |
| grant-and-share | 明确文档 ID 拼写仅映射 `node` | 命令级别名 | `url` 与 `node` 两个真实角色不混合 |
## 四、当前能力无法解决或不应解决
| 场景 | 为什么别名表不能处理 | 当前安全做法 | 未来能力 |
|---|---|---|---|
| `content-file` 裸路径变正文 | 需要读文件或补 `@`,属于值转换 | block 并给出替代命令 | 受控参数转换器/文件输入类型 |
| `include blocks` | 需要读取值并选择参数或切换命令 | block,提示 `+fetch` | 值依赖展开/路由器 |
| nodeId 变共享 URL | 需要查询或构造 URL | block,要求真实 `--url` | 受控编排转换 |
| 无角色 `block-id` | 起点/终点/父块/参考块均可能 | ambiguous | 多参数角色解析 |
| 原生 hidden `--id` 过宽 | 已被 Cobra 接受,中央预解析不接管 | 保持现状并单独治理 | 原生 alias 审核/收窄 |
| `doc export --format` 冲突 | 是真实本地 flag 与全局 flag 冲突 | 不新增别名 | 删除/迁移真实兼容 flag |
| 参数组合互斥/依赖 | 不是参数名问题 | 依赖运行时校验 | 完整 Contract constraints |
| 接收人名称解析/单复数转换 | 需要查人、拆分或合并值 | 不自动转换 | 类型化 resolver |
## 五、验证结果与剩余门禁
已完成:
- 当前二进制构建成功;
- 90 个 Schema 工具与 Help 的公开参数名对账,差异 0;
- 41 个 Skill 文件、955 条 Doc 代码片段审计,无真实 executable flag 漂移;
- 候选参数表 JSON 校验通过;
- 隔离副本执行 `go generate ./internal/cli` 成功,输出 281 个命令映射;
- 候选命令名表与参数表共同生成成功,输出 34 条命令路径兜底;
- 参数 fixture 已兼容布尔参数的 `--flag=value` 规范形式,`include-versions → include-history=true` 不再被误判为丢值;
- 7 个新增 active 命令均补齐完整业务调用模板,10 个新增 active alias 均完成 canonical/alias 最终 transport payload 等价验证;
- `+access-grant`、`+history-revert`、`+update` 的 alias 回放仍由原命令返回 `confirmation_required`,没有绕过确认;
- media/resource 的 `file-id/url` 回放返回 `ambiguous_flag`,强文档身份 `document-id → node` 可正常进入目标命令;
- `+create-version`、`+save-version`、`+snapshot`、`+version-create` 均保留原始 `--node` 并改写到 `+history-save`,mock 回放实际调用 `save_doc_version`;
- `+export-pdf` 负向回放仍返回 `unknown_shortcut`,没有被路径兜底误导到缺少 `--export-format pdf` 的导出流程;
- 更新隔离副本的预期数量/覆盖 fixture 后,相关 `internal/cli`、`internal/app` 参数/命令兜底测试和 `internal/pipeline` 命令兜底测试通过。
尚未执行的正式门禁:候选尚未同步到 `internal/cli`,因此没有在工作分支运行全量 `go test ./internal/app`、generated drift 和 Schema policy。正式实施时还必须同步测试期望(fallback 总数由 26 变 34,并增加 8 条 Doc 覆盖),再执行 Spec 第 9 节的完整命令。
## 六、第一轮实施建议
1. 先同步候选参数表,并补 payload 等价、blocked/ambiguous、原生 flag 不受影响的回归测试。
2. 同步 4 条可由现框架表达的 Doc shortcut 名兜底及覆盖测试;不加入跨 shortcut/non-shortcut 自动改写。
3. 对 `content-file`、`include`、share URL、block role 保持 fail-closed。
4. 单独立项处理原生 hidden `--id`、export `--format`、非叶子命令及参数组合约束。
5. 完整运行生成漂移、Schema Catalog 和 app 集成门禁后,再决定是否合入正式文件。
## 七、可复用审计流程
1. 从当前 Cobra 树枚举真实命令、公开/隐藏 flags 和 runnable 状态;
2. 用运行时组装 Schema 对账每个公开 leaf;
3. 扫描 Skill 中真正可执行的代码片段,并剔除文档记法;
4. 按业务实体、角色、单复数和值域聚合参数,而不是按字符串相似度聚合;
5. 只对“值可原样传递”的映射使用 concept/override,其余进入 block/ambiguous/当前不支持;
6. 在隔离副本生成并测试,最后才同步正式输入文件;
7. 实验 badcase 单列附录,用来验证静态结论和发现命令名幻觉,不取代当前契约事实。
@@ -0,0 +1,139 @@
# DWS Doc 实验参数与命令名幻觉逐 case 观察
> 分析日期:2026-08-06
> 数据目录:`/Users/hyz/works/data/doc`
> 当前契约基线:`fix/param-hallucination@b94e2133`
> 说明:本报告是静态审计的实验附录。分类时先解析完整命令路径,再判断 flags;不能因为输出含 `unknown flag` 就直接认定为参数幻觉。
## 结论摘要
对 `/Users/hyz/works/data/doc` 下 6 组独立数据集逐 case 去重后,共审计 **388 个 case-run、1,644 次 `dws doc` 调用**。最终确认:
- 4 次调用存在当前仍未覆盖的参数名幻觉,共涉及 6 个错误 flag;
- 6 次调用使用了当前参数表已经能兜底的参数名;
- 7 次调用存在 shortcut 命令名幻觉,涉及 5 个不存在的 shortcut 名;
- 5 次调用使用不存在的普通路径 `doc fetch`;其中 4 次因为携带 flag,被父命令先报成 `unknown flag`,实际根因不是参数;
- 79 次调用使用实验基线当时不存在、但当前已经真实存在的命令,属于版本演进,不应配置 fallback;
- 其余主要是当前契约可接受调用,或认证、业务数据、值/约束等非参数名问题。
## 一、数据集与去重口径
| 数据集 | case-run | Doc 调用 |
|---|---:|---:|
| v2 mono | 65 | 277 |
| v2 multi | 65 | 258 |
| audit multi(精确 `000bc134`) | 65 | 260 |
| v3 mono | 64 | 264 |
| v3 multi | 64 | 300 |
| normalized trajectories | 65 | 285 |
| 合计 | 388 | 1,644 |
timeout rerun 已经并入各数据集最终结果,没有把 ZIP、Markdown、HTML 或报告副本重复计数。
## 二、总分类
| 分类 | 调用数 | 判断 |
|---|---:|---|
| 当前契约可接受 | 1,457 | 当前路径和参数可被真实 CLI 接受 |
| 版本演进:当前命令已存在 | 79 | 不是当前幻觉,不配 fallback |
| 非命令名/参数名错误 | 71 | 认证、网络、业务数据或其他错误 |
| 参数值或约束候选 | 15 | 需要独立业务语义复核,不计入参数名结论 |
| 当前参数别名已兜底 | 6 | 当前正式参数表已能归一 |
| 当前未覆盖参数名幻觉 | 4 | 本轮参数候选需要处理 |
| shortcut 命令名幻觉 | 7 | 2 条安全 rewrite、2 条 ambiguous、1 条跨层暂不支持 |
| 普通子命令路径幻觉 | 5 | 都是 `doc fetch`,现框架不能跨层 rewrite |
“参数值或约束候选”来自保守正则筛选,不能仅凭错误文本认定为参数名幻觉,因此本报告不把它们加入兜底数量。
## 三、已由当前参数表解决的 badcase
| 实验调用 | 次数 | 当前归一结果 | 验证 |
|---|---:|---|---|
| `doc +template-search --keyword ...` | 3 | `keyword → query` | 当前二进制 mock 回放成功 |
| `doc +doc-append --node ... --text ...` | 1 | `node → doc`,`text` 原生归入正文概念 | dry-run/payload 等价验证 |
| `doc +doc-append --node ... --content ...` | 1 | `node → doc`,`content → text` | dry-run/payload 等价验证 |
| `doc +comment-create --text ...` | 1 | `text → content` | dry-run/payload 等价验证 |
这 6 次不能再计为当前缺陷,说明现有参数概念层对高频跨 shortcut 名称迁移已经有效。
## 四、当前未覆盖的参数名幻觉
| Case | 实验调用 | 错误 flag | 正确契约 | 建议 |
|---|---|---|---|---|
| trajectories `dws_doc_0003` | `doc +inspect --include blocks` | `--include` | blocks 正文应使用 `doc +fetch`;inspect 没有通用 include | block,提示按 section 选择真实 flag/命令 |
| trajectories `dws_doc_0006` | `doc +inspect --include-info --include-versions` | `--include-info`、`--include-versions` | 基础 info 总是返回;历史 section 是 `--include-history` | block include-info;rewrite include-versions |
| trajectories `dws_doc_0013` | `doc +create --content-format markdown` | `--content-format` | `--doc-format markdown` | concept 自动映射 |
| trajectories `dws_doc_0025` | `doc +create --content-file PATH --content-format markdown` | `--content-file`、`--content-format` | `--content @relative-path`/stdin,或稳定 `doc create --content-file`;格式为 `--doc-format` | block file 参数;格式自动映射 |
4 次调用包含 6 个错误 flag。只有 `content-format` 和 `include-versions` 满足“同实体、同角色、原值透传”;另外 3 种 spellings 需要值变换、命令切换或本来就是冗余输入,必须 fail-closed。
## 五、shortcut 命令名幻觉
| 不存在的路径 | 次数 | 当前真实候选 | 处理结论 |
|---|---:|---|---|
| `doc +list-templates` | 1 | `doc +template-list` | 唯一只读语义,安全 rewrite |
| `doc +search-template` | 1 | `doc +template-search` | 动宾倒置,唯一只读语义,安全 rewrite |
| `doc +template` | 2 | `+template-list`、`+template-search`、`+create-from-template` | umbrella 名,ambiguous,停止执行 |
| `doc +version` | 2 | `+history-list`、`+history-save`、`+history-revert` | 包含读、写和高风险 revert,ambiguous,停止执行 |
| `doc +rename` | 1 | 普通稳定命令 `doc rename` | 现框架禁止 shortcut→非-shortcut 跨身份改写;暂不入表 |
候选 `command_path_fallbacks.json` 对这一批实验加入前 4 条:2 个 rewrite 和 2 个 ambiguous。fallback 只改变命令路径,不修改 flag/value;rewrite 后仍由真实目标命令完成参数和安全校验,ambiguous 在参数解析和 dispatch 前停止。
### 5.1 补充版本快照 badcase
后续补充 badcase 在“保存当前文档历史快照”的同一意图下,连续尝试了 `+create-version`、`+save-version`、`+snapshot`、`+export-pdf`、`+version-create`,最后才找到当时可用的 `+version-save`。按当前命令契约复核:
- `+create-version`、`+save-version`、`+snapshot`、`+version-create` 都表示保存当前版本快照,参数只需原样保留 `--node`,安全 rewrite 到当前 canonical `doc +history-save`;
- `+export-pdf` 表示另一类导出意图,而且需要补 `--export-format pdf`。当前 fallback 只允许改路径、不能注入 const 参数,因此保持 `unknown_shortcut`,不能错误改写到默认导出 docx 的 `+export`。
候选表据此再增加 4 条精确 rewrite,Doc 增量合计为 8 条。
隔离副本生成后逐条 mock 回放,四个名称都原样保留 `--node` 并实际进入 `save_doc_version`;`+export-pdf` 的负向回放仍返回 `unknown_shortcut`。这同时验证了兜底没有注入参数、没有把导出误当成历史版本保存。
## 六、`unknown flag` 掩盖了普通子命令错误
实验共出现 5 次 `doc fetch`。当前真实路径是 `doc +fetch`,普通路径 `doc fetch` 不存在:
- 4 次调用携带 `--node`、`--detail` 或输出 flag,父级 `doc` 先把它们报成 `unknown flag`;
- 1 次去掉 flag 后才明确显示 `unknown subcommand "fetch"`。
所以这 4 次是“子命令错误被 unknown flag 掩盖”,不是 4 个参数 hallucination。把 `node/detail` 塞进参数别名表既不能创建 `doc fetch`,还会污染正确命令。
现有命令 fallback 框架要求 source/target 保持相同 shortcut 身份,不能加入 `doc fetch → doc +fetch`;这是正确的安全边界。可选的未来方案是:
1. 为 `doc fetch` 声明真实 CLI alias/兼容叶子,并在命令身份层审核;或
2. 改进父命令错误解析顺序,在存在未识别 path token 时优先返回 unknown subcommand,并提示 `doc +fetch`。
## 七、版本演进项
79 次调用在实验 commit `000bc134` 不存在,但当前已经是真实命令:
| 当前真实路径 | 次数 |
|---|---:|
| `doc +fetch` | 38 |
| `doc +create-from-template` | 20 |
| `doc +create` | 8 |
| `doc +update` | 8 |
| `doc +inspect` | 2 |
| `doc +media-list` | 1 |
| `doc +history-save` | 1 |
| `doc +history-list` | 1 |
这些是 Schema/shortcut 覆盖扩展后的正常路径。为它们再配置 fallback 会与真实 Cobra 命令发生 source collision,因此必须排除。
## 八、建议同步的候选文件
- `docs/parameter-hallucination/doc/param_concepts.json`:完整正式表基线上的 Doc 参数候选;
- `docs/parameter-hallucination/doc/command_path_fallbacks.json`:完整正式表基线 + 8 条 Doc 命令名候选。
候选命令表不是只含 Doc 的增量文件;保留正式表的 Chat/OA 条目,可以在隔离副本执行完整生成和回归测试。正式同步时还需将 fallback 测试期望从 26 更新到 34,并补齐 8 条 Doc audit coverage。
## 九、风险与负面影响审查
- 不新增真实 CLI flag,不改变 Schema flag 权威;
- 不跨产品、不中途改变参数值,不读取文件,不查询 URL;
- 不把单值变多值,也不把版本、revision、comment、block、job/task ID 互换;
- 不自动选择带写入或高风险的 umbrella shortcut;
- rewrite 后目标命令的 required/enum/constraints/confirmation 继续生效;
- ambiguous/block 在执行前返回,不会触发业务调用;
- 不给已经真实存在的新 shortcut 配 fallback,避免碰撞和长期陈旧映射。
@@ -0,0 +1,434 @@
{
"$schema": "./param_concepts.schema.json",
"version": 1,
"morphological_rules": {
"kebab_camel_equivalence": {"desc": "--page-size == --pageSize", "enabled": true},
"separator_normalization": {"desc": "-, _, . are equivalent separators", "enabled": true},
"trailing_id_tolerance": {"desc": "--base tolerates --base-id when only one is a real flag on the command", "enabled": true, "guard": "the two must not both be real flags with different semantics"},
"pluralization": {"desc": "--id<->--ids, --user<->--users", "enabled": false, "reason": "singular/list semantics can differ; handled by concept+intersection or command override instead"}
},
"concepts": {
"search_query": {"denotes": "search keyword string", "canonical_hint": "query", "members": ["query", "keyword", "keywords", "q", "search-word"], "excludes": ["name", "subject", "text", "title"], "commands": ["aitable +base-search", "contact +dept-members", "contact +resolve-dept", "contact +search-user", "doc +create-from-template", "doc +find-doc", "doc +search", "doc +template-search", "doc template search", "mail +find-mail-user", "mail user search", "oa +search-forms", "oa approval search-forms"], "risk": "green"},
"pagination_size": {"denotes": "returned item count upper bound", "canonical_hint": "limit", "members": ["limit", "size", "page-size", "max-results", "max-result", "take", "top", "per-page"], "excludes": ["count", "page", "cursor"], "commands": ["aitable record query", "calendar event list", "chat message list", "devdoc article search", "doc +comment-list", "doc +find-doc", "doc +list", "doc +search", "doc +template-list", "doc +template-search", "doc +version-list", "doc comment list", "doc template list", "doc template search", "doc version list", "mail thread list", "oa +list-executed"], "risk": "green"},
"page_number": {"denotes": "one-based page number", "canonical_hint": "page", "members": ["page", "page-no", "current-page", "page-num"], "excludes": ["cursor", "page-index", "page-size", "page-token"], "commands": ["devdoc article search"], "risk": "green"},
"page_cursor": {"denotes": "pagination cursor/token", "canonical_hint": "cursor", "members": ["cursor", "next-cursor", "page-token", "next-token", "next-page-token"], "excludes": ["page", "offset"], "commands": ["calendar event list", "doc +comment-list", "doc +list", "doc +search", "doc +template-list", "doc +template-search", "doc +version-list", "doc comment list", "doc template list", "doc template search", "doc version list"], "risk": "green"},
"content_text": {"denotes": "text body content", "canonical_hint": "text", "members": ["text", "content", "body"], "excludes": ["title", "name"], "commands": ["doc +checkpoint-update", "doc +comment-create", "doc +comment-reply", "doc +comment-update", "doc +create", "doc +doc-append", "doc block insert", "doc block update", "doc comment create", "doc comment create-inline", "doc comment reply", "doc comment update", "doc create"], "risk": "green"},
"time_start": {"denotes": "start time point with unchanged value format and unit", "canonical_hint": "start", "members": ["start", "start-time", "start-date", "from", "from-date", "begin", "since", "time-min", "min-time"], "excludes": ["date", "time", "end"], "commands": ["calendar event list", "chat message list-all", "report list"], "risk": "yellow"},
"time_end": {"denotes": "end time point with unchanged value format and unit", "canonical_hint": "end", "members": ["end", "end-time", "end-date", "time-max", "max-time"], "excludes": ["date", "time", "start"], "commands": ["calendar event list"], "risk": "yellow"},
"base_id": {"denotes": "multi-dimensional table Base id", "canonical_hint": "base-id", "members": ["base", "base-id", "base-token"], "excludes": [], "commands": ["aitable +field-get", "aitable +list-tables", "aitable +record-query", "aitable +record-share-url", "aitable +table-get"], "risk": "green"},
"dept_id": {"denotes": "single department id", "canonical_hint": "dept", "members": ["dept", "dept-id", "department", "department-id", "parent", "parent-id"], "excludes": ["depts", "dept-ids", "department-ids", "name", "query"], "commands": ["contact +list-sub-depts", "contact dept list-children"], "risk": "yellow"},
"dept_ids": {"denotes": "department id list", "canonical_hint": "dept-ids", "members": ["depts", "dept-ids", "department-ids"], "excludes": ["dept", "dept-id", "department-id", "name", "query"], "commands": ["contact +list-dept-members"], "risk": "yellow"},
"group_id": {"denotes": "single DingTalk numeric groupId", "canonical_hint": "group-id", "members": ["group-id"], "excludes": ["group", "conversation-id", "chat", "chat-id", "open-conversation-id", "conversation-ids", "open-conversation-ids", "group-name", "name", "id"], "commands": ["chat +chat-get-by-id", "chat group get-by-group-id"], "risk": "yellow"},
"open_conversation_id": {"denotes": "single DingTalk openConversationId with unchanged value", "canonical_hint": "conversation-id", "members": ["group", "conversation-id", "chat", "chat-id", "open-conversation-id"], "excludes": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids", "group-name", "name", "id", "source", "target", "src-conversation-id", "dest-conversation-id"], "commands": ["chat +category-add-conversation", "chat +category-remove-conversation", "chat +chat-add-bot", "chat +chat-audit-join", "chat +chat-bots", "chat +chat-dismiss", "chat +chat-invite-url", "chat +chat-members-get", "chat +chat-mute", "chat +chat-mute-member", "chat +chat-quit", "chat +chat-remove-bot", "chat +chat-role-add", "chat +chat-role-list", "chat +chat-role-query-user", "chat +chat-role-remove", "chat +chat-role-remove-user", "chat +chat-role-set-user", "chat +chat-role-update", "chat +chat-set-admin", "chat +chat-set-history", "chat +chat-transfer-owner", "chat +chat-update-alias", "chat +chat-update-icon", "chat +chat-update-nick", "chat +chat-update-settings", "chat +conversation-clear-messages", "chat +conversation-clear-red-point", "chat +conversation-hide", "chat +conversation-info", "chat +conversation-mark-read", "chat +conversation-mark-unread", "chat +conversation-mute", "chat +flag-cancel", "chat +flag-create", "chat +messages-add-emoji", "chat +messages-add-text-emotion", "chat +messages-list-pin", "chat +messages-read-status", "chat +messages-recall-by-bot", "chat +messages-remove-emoji", "chat +messages-remove-text-emotion", "chat +messages-reply", "chat +messages-resource-download", "chat +messages-resource-url", "chat +messages-send-by-bot", "chat +messages-set-pin", "chat +messages-set-top", "chat +messages-unset-pin", "chat +messages-unset-top", "chat category add-conv", "chat category remove-conv", "chat chmod", "chat clear-messages", "chat clear-red-point", "chat conversation-info", "chat group audit-join-validation", "chat group bots", "chat group dismiss", "chat group invite-url", "chat group members", "chat group members add", "chat group members add-bot", "chat group members list-by-ids", "chat group members remove", "chat group members remove-bot", "chat group notice create", "chat group notice edit", "chat group notice get", "chat group notice list", "chat group quit", "chat group rename", "chat group set-admin", "chat group set-history", "chat group transfer-owner", "chat group update-alias", "chat group update-icon", "chat group update-nick", "chat group update-settings", "chat group-mute", "chat group-mute-member", "chat group-role add", "chat group-role list", "chat group-role query-user", "chat group-role remove", "chat group-role remove-user", "chat group-role set-user", "chat group-role update", "chat hide", "chat mark-read", "chat mark-unread", "chat message add-favorite", "chat message download-media", "chat message list", "chat message list-mentions", "chat message list-pin-msg", "chat message list-topic-replies", "chat message read-status", "chat message recall", "chat message recall-by-bot", "chat message remove-favorite", "chat message reply", "chat message search", "chat message send", "chat message send-by-bot", "chat message send-card", "chat message set-pin-msg", "chat message set-top-msg", "chat message unset-pin-msg", "chat message unset-top-msg", "chat mute-at-all", "chat mute-red-envelope", "chat set-top"], "risk": "yellow"},
"open_conversation_ids": {"denotes": "DingTalk openConversationId list with unchanged element values", "canonical_hint": "conversation-ids", "members": ["conversation-ids", "open-conversation-ids", "groups"], "excludes": ["group-id", "group-ids", "conversation-id", "open-conversation-id", "chat-id"], "commands": ["chat message search-advanced"], "risk": "yellow"},
"group_name": {"denotes": "group-name search keyword, not a group identifier", "canonical_hint": "group-name", "members": ["group-name"], "excludes": ["group-id", "conversation-id", "open-conversation-id", "chat-id", "id"], "commands": ["chat +group-members", "chat +send-to-group"], "risk": "yellow"},
"open_message_id": {"denotes": "single DingTalk openMessageId with unchanged value", "canonical_hint": "open-message-id", "members": ["msg-id", "message-id", "open-message-id"], "excludes": ["msg-ids", "message-ids", "open-message-ids", "ref-msg-id", "src-msg-id", "open-task-id", "topic-id", "resource-id"], "commands": ["chat +conversation-mark-read", "chat +flag-cancel", "chat +flag-create", "chat +messages-add-emoji", "chat +messages-add-text-emotion", "chat +messages-forward", "chat +messages-read-status", "chat +messages-remove-emoji", "chat +messages-remove-text-emotion", "chat +messages-resource-download", "chat +messages-set-pin", "chat +messages-set-top", "chat +messages-unset-pin", "chat +messages-unset-top", "chat mark-read", "chat message add-emoji", "chat message add-favorite", "chat message add-text-emotion", "chat message download-media", "chat message forward", "chat message read-status", "chat message recall", "chat message remove-emoji", "chat message remove-favorite", "chat message remove-text-emotion", "chat message set-pin-msg", "chat message set-top-msg", "chat message unset-pin-msg", "chat message unset-top-msg"], "risk": "yellow"},
"open_message_ids": {"denotes": "DingTalk openMessageId list with unchanged element values", "canonical_hint": "msg-ids", "members": ["msg-ids", "message-ids", "open-message-ids"], "excludes": ["msg-id", "message-id", "open-message-id", "ref-msg-id", "src-msg-id"], "commands": ["chat +flag-cancel", "chat +flag-create", "chat +messages-combine-forward", "chat +messages-mget", "chat message combine-forward", "chat message list-by-ids", "chat message list-emotion-replies"], "risk": "yellow"},
"referenced_open_message_id": {"denotes": "referenced DingTalk openMessageId in a reply", "canonical_hint": "ref-msg-id", "members": ["ref-msg-id", "ref-message-id"], "excludes": ["msg-id", "message-id", "open-message-id", "msg-ids", "src-msg-id"], "commands": ["chat +messages-reply", "chat message reply"], "risk": "yellow"},
"user_id": {"denotes": "single user id", "canonical_hint": "user-id", "members": ["user", "user-id", "userid", "uid", "staff-id"], "excludes": ["at-user-ids", "to-user", "users", "user-ids", "name"], "commands": ["chat +chat-role-query-user", "chat +chat-role-set-user", "chat +messages-list-direct", "chat chmod", "chat conversation-info", "chat group transfer-owner", "chat group-role query-user", "chat group-role remove-user", "chat group-role set-user", "chat message list", "chat message send", "contact user profile get"], "risk": "yellow"},
"user_ids": {"denotes": "user id list", "canonical_hint": "user-ids", "members": ["users", "user-ids"], "excludes": ["user", "user-id", "userid", "uid", "staff-id", "at-user-ids"], "commands": ["attendance +check-result", "attendance check result", "chat +messages-batch-send-by-bot", "chat group members remove", "chat group set-admin", "chat group-mute-member", "chat message read-status", "chat message search-advanced", "chat message send-by-bot"], "risk": "yellow"},
"open_dingtalk_ids": {"denotes": "DingTalk openDingTalkId list with unchanged element values", "canonical_hint": "open-dingtalk-ids", "members": ["open-dingtalk-ids"], "excludes": ["user", "user-id", "user-ids", "staff-id", "users"], "commands": ["chat +chat-members-get", "chat category create-smart", "chat group members list-by-ids", "chat message send-by-bot"], "risk": "yellow"},
"ding_id": {"denotes": "DING id", "canonical_hint": "ding-id", "members": ["ding-id", "open-ding-id"], "commands": ["ding message receiver-status"], "risk": "yellow"},
"folder_id": {"denotes": "drive folder id", "canonical_hint": "folder", "members": ["folder", "folder-id"], "excludes": ["space-id"], "commands": ["drive list", "mail folder update"], "risk": "green"},
"space_id": {"denotes": "drive/wiki/Doc workspace id with unchanged value", "canonical_hint": "space-id", "members": ["space-id", "space", "workspace", "workspace-id"], "excludes": ["folder", "node"], "commands": ["doc +access-change", "doc +access-grant", "doc +access-revoke", "doc +copy", "doc +create", "doc +create-from-template", "doc +grant-and-share", "doc +import", "doc +list", "doc +move", "doc create", "doc file create", "doc import", "doc template apply", "drive info"], "risk": "yellow"},
"app_id": {"denotes": "application id", "canonical_hint": "unified-app-id", "members": ["app-id", "unified-app-id", "application-id"], "excludes": ["app-key", "app-secret", "agent-id"], "commands": ["dev app get"], "risk": "yellow"},
"robot_code": {"denotes": "robot code", "canonical_hint": "robot-code", "members": ["robot-code", "robot"], "excludes": ["robot-id", "bot-id", "open-bot-id", "bot-code"], "commands": ["chat +chat-add-bot", "chat +messages-batch-recall-by-bot", "chat +messages-batch-send-by-bot", "chat +messages-recall-by-bot", "chat +messages-send-by-bot", "chat group members add-bot", "chat message recall-by-bot", "chat message send-by-bot", "ding message send"], "risk": "yellow"},
"open_bot_id": {"denotes": "single DingTalk openBotId with unchanged value", "canonical_hint": "bot-id", "members": ["bot-id", "open-bot-id"], "excludes": ["robot-code", "robot", "robot-id", "bot-code"], "commands": ["chat +chat-remove-bot", "chat group members remove-bot"], "risk": "yellow"},
"doc_node_id": {"denotes": "single DingTalk document nodeId or accepted document URL/token with unchanged value", "canonical_hint": "node", "members": ["node", "node-id", "doc", "doc-id", "file-id", "document-id", "url"], "excludes": ["id", "folder", "folder-id", "parent-id", "workspace", "workspace-id", "block-id", "comment-id", "comment-key", "job-id", "task-id", "template-id", "version", "revision"], "commands": ["doc +access-change", "doc +access-grant", "doc +access-revoke", "doc +background-delete", "doc +background-update", "doc +checkpoint-update", "doc +comment-create", "doc +comment-delete", "doc +comment-list", "doc +comment-reply", "doc +comment-update", "doc +copy", "doc +doc-append", "doc +export", "doc +export-submit", "doc +fetch", "doc +history-list", "doc +history-revert", "doc +history-save", "doc +inspect", "doc +move", "doc +review", "doc +version-list", "doc +version-revert", "doc +version-save", "doc block delete", "doc block insert", "doc block list", "doc block update", "doc comment create", "doc comment create-inline", "doc comment delete", "doc comment list", "doc comment reply", "doc comment update", "doc export", "doc info", "doc media download", "doc media insert", "doc media upload", "doc read", "doc style background clear", "doc style background set", "doc style cover clear", "doc style cover set", "doc style get", "doc update", "doc version list", "doc version revert", "doc version save", "doc whiteboard insert"], "risk": "yellow"},
"doc_comment_key": {"denotes": "single DingTalk document commentKey with unchanged value", "canonical_hint": "comment-key", "members": ["comment-key", "comment-id"], "excludes": ["id", "node", "node-id", "doc-id", "block-id"], "commands": ["doc +comment-delete", "doc +comment-reply", "doc +comment-update", "doc comment delete", "doc comment reply", "doc comment update"], "risk": "yellow"},
"doc_version_number": {"denotes": "single DingTalk document historical version number with unchanged integer value", "canonical_hint": "version", "members": ["version", "version-number", "version-no"], "excludes": ["revision", "id", "node", "node-id", "doc-id"], "commands": ["doc +history-revert", "doc +version-revert", "doc version revert"], "risk": "yellow"},
"doc_content_format": {"denotes": "DingTalk document body format with unchanged markdown/jsonml value", "canonical_hint": "content-format", "members": ["content-format", "doc-format"], "excludes": ["format", "export-format", "mime-type"], "commands": ["doc +create", "doc +update", "doc create", "doc update"], "risk": "green"},
"doc_edit_revision": {"denotes": "single optimistic-concurrency revision for a document edit", "canonical_hint": "revision", "members": ["revision", "expected-revision"], "excludes": ["version", "version-number", "version-no"], "commands": ["doc +update", "doc update"], "risk": "yellow"}
},
"command_overrides": {
"chat group rename": {"bind": {"id": "open_conversation_id"}, "note": "This command's real --id carries one openConversationId; aliases reduce to --id without changing the value."},
"chat group members": {"bind": {"id": "open_conversation_id"}},
"chat group members add": {"bind": {"id": "open_conversation_id"}, "block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a list and may contain mixed userId/openDingTalkId values; singular inputs are not promoted automatically."},
"chat group members remove": {"bind": {"id": "open_conversation_id"}},
"chat message add-emoji": {"scoped_aliases": {"chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat message add-text-emotion": {"scoped_aliases": {"chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat message remove-emoji": {"scoped_aliases": {"chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat message remove-text-emotion": {"scoped_aliases": {"chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat mute": {"scoped_aliases": {"group": "conversation-id", "chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --conversation-id/--id/--chat remain unchanged; other reviewed openConversationId spellings reduce to --conversation-id."},
"drive list": {"ambiguous": ["space"], "note": "both --space-id and native compatibility --workspace-id/--workspace exist; bare --space cannot choose one"},
"drive upload": {"ambiguous": ["space"], "note": "both --space-id and native compatibility --workspace-id/--workspace exist; bare --space cannot choose one"},
"ding +receiver-status": {"scoped_aliases": {"id": "ding-id"}, "note": "generic id reduces to ding-id"},
"ding message receiver-status": {"scoped_aliases": {"id": "ding-id"}},
"contact user profile get": {"scoped_aliases": {"id": "staff-id", "ids": "staff-id"}, "note": "user-id is reduced by the user_id concept; generic id/ids bound explicitly"},
"mail folder update": {"bind": {"id": "folder_id"}, "note": "this command's --id is the folder id; --folder-id reduces to --id"},
"mail message search": {"scoped_aliases": {"subject": "query"}, "scope_strict": true, "note": "never globalize: mail template create has a real and different --subject"},
"calendar event list": {"scoped_aliases": {"date": "start"}, "note": "reviewed against ParseISOTimeToMillis and final list_calendar_events payload; --date is normalized centrally while the command's existing hidden compatibility flags remain native fallbacks"},
"chat +bot-find": {"scoped_aliases": {"name": "query"}, "scope_strict": true, "note": "On this exact bot-search shortcut, --name and --query denote the same search keyword; --name must not become a global search alias."},
"chat +chat-messages": {"scoped_aliases": {"chat": "group"}, "scope_strict": true, "note": "Evaluation compatibility: --chat carries one stable openConversationId and normalizes to the existing --group identifier route."},
"chat +search-msg": {"scoped_aliases": {"chat": "group"}, "scope_strict": true, "note": "Evaluation compatibility: scalar --chat carries one stable openConversationId and normalizes to the existing scalar --group filter."},
"chat bot find": {"scoped_aliases": {"name": "query"}, "scope_strict": true, "note": "On this exact bot-search command, --name and --query denote the same search keyword; --name must not become a global search alias."},
"chat +bot-search": {"scoped_aliases": {"query": "name", "current-page": "page"}, "block": ["cursor"], "scope_strict": true, "note": "Only the reviewed bot keyword and page-number spellings are accepted; cursor pagination cannot be converted to a page number."},
"chat bot search": {"scoped_aliases": {"query": "name", "current-page": "page"}, "block": ["cursor"], "scope_strict": true, "note": "Only the reviewed bot keyword and page-number spellings are accepted; cursor pagination cannot be converted to a page number."},
"chat message list-favorites": {"scoped_aliases": {"limit": "size"}, "scope_strict": true, "note": "On this exact command, both names denote the same bounded result count; the numeric value is unchanged."},
"chat +messages-list-unread-conversations": {"scoped_aliases": {"limit": "count", "size": "count"}, "scope_strict": true, "note": "On this exact command, limit and size both denote the returned unread-conversation count."},
"chat +unread-chats": {"scoped_aliases": {"limit": "count", "size": "count"}, "scope_strict": true, "note": "On this exact command, limit and size both denote the returned unread-conversation count."},
"chat message list-unread-conversations": {"scoped_aliases": {"limit": "count", "size": "count"}, "scope_strict": true, "note": "On this exact command, limit and size both denote the returned unread-conversation count."},
"chat +messages-list-direct": {"scoped_aliases": {"start": "time"}, "block": ["end"], "scope_strict": true, "note": "This exact command accepts one start boundary in yyyy-MM-dd HH:mm:ss; an end-only input cannot be represented."},
"chat message list": {"scoped_aliases": {"start": "time"}, "block": ["end"], "scope_strict": true, "note": "This exact command accepts one start boundary in yyyy-MM-dd HH:mm:ss; an end-only input cannot be represented."},
"chat message list-by-sender": {"scoped_aliases": {"user-id": "sender-user-id", "open-dingtalk-id": "sender-open-dingtalk-id"}, "block": ["time"], "scope_strict": true, "note": "Only same-role sender identifiers are mapped; --time cannot supply the required RFC3339 start/end range."},
"contact +resolve-dept": {"bind": {"name": "search_query"}, "note": "The real --name is a department-name search keyword and carries the search_query concept on this shortcut."},
"contact +list-sub-depts": {"block": ["name", "query"], "note": "--dept is an integer department id; names and search queries require a separate resolution command"},
"contact +dept-members": {"bind": {"dept": "search_query"}, "scoped_aliases": {"name": "dept"}, "note": "The real --dept is a department-name search keyword; search spellings come from search_query, while --name remains command-scoped."},
"chat message send": {"scoped_aliases": {"to-user": "user", "file": "file-path"}, "note": "Recipient and local-file-path aliases are exact to this command; obsolete file metadata flags remain unsupported."},
"chat +group-members": {"bind": {"group": "group_name"}, "note": "The real --group is a group-name search keyword on this shortcut, not an identifier."},
"chat +category-create": {"scoped_aliases": {"name": "title"}, "scope_strict": true, "note": "The reviewed name/title mapping preserves the category display-name value on this exact shortcut."},
"chat category create": {"scoped_aliases": {"name": "title"}, "scope_strict": true, "note": "The reviewed name/title mapping preserves the category display-name value on this exact command."},
"chat +category-rename": {"scoped_aliases": {"name": "title"}, "block": ["category-ids"], "scope_strict": true, "note": "The display-name alias is exact; a category-id list is not accepted where one category is required."},
"chat category rename": {"scoped_aliases": {"name": "title"}, "block": ["category-ids"], "scope_strict": true, "note": "The display-name alias is exact; a category-id list is not accepted where one category is required."},
"chat +category-delete": {"block": ["category-ids"], "note": "This command requires one category id; list cardinality is not reduced automatically."},
"chat category delete": {"block": ["category-ids"], "note": "This command requires one category id; list cardinality is not reduced automatically."},
"chat category list-conversations": {"block": ["category-ids"], "note": "This command requires one category id; list cardinality is not reduced automatically."},
"chat category add-conv": {"block": ["category-id"], "note": "This command requires a category-id list; one id is not promoted into a batch input."},
"chat category remove-conv": {"block": ["category-id"], "note": "This command requires a category-id list; one id is not promoted into a batch input."},
"chat +chat-role-update": {"block": ["role-ids"], "note": "This command requires one role id; list cardinality is not reduced automatically."},
"chat group-role remove": {"block": ["role-ids"], "note": "This command requires one role id; list cardinality is not reduced automatically."},
"chat group-role update": {"block": ["role-ids"], "note": "This command requires one role id; list cardinality is not reduced automatically."},
"chat +chat-role-set-user": {"block": ["role-id"], "note": "This command requires a role-id list; one id is not promoted into a batch input."},
"chat group-role remove-user": {"block": ["role-id"], "note": "This command requires a role-id list; one id is not promoted into a batch input."},
"chat group-role set-user": {"block": ["role-id"], "note": "This command requires a role-id list; one id is not promoted into a batch input."},
"chat +messages-send-by-webhook": {"scoped_aliases": {"at-user-ids": "at-users"}, "scope_strict": true, "note": "Both names denote the same userId list used for @ mentions on this exact shortcut."},
"chat message send-by-webhook": {"scoped_aliases": {"at-user-ids": "at-users"}, "scope_strict": true, "note": "Both names denote the same userId list used for @ mentions on this exact command."},
"doc block insert": {"block": ["before-block-id"], "note": "Parent and reference roles remain distinct. --before-block-id needs both --ref-block and --where before, while role-free --block-id cannot choose parent versus reference.", "scoped_aliases": {"parent-block-id": "parent-block", "ref-block-id": "ref-block", "reference-block-id": "ref-block"}, "ambiguous": ["block-id"], "scope_strict": true},
"chat message send-by-bot": {"scoped_aliases": {"at-users": "at-user-ids"}, "block": ["user-id", "to-user-id"], "ambiguous": ["at-ids"], "note": "The reviewed @ userId-list alias is exact; singular recipients are not promoted, and bare --at-ids cannot choose an identifier domain."},
"doc +export-get": {"block": ["doc-id", "document-id", "file-id", "node", "node-id", "task-id", "url"], "note": "This command queries one export jobId. Document node identifiers and import taskId spellings are different entities and are rejected.", "scoped_aliases": {"export-job-id": "job-id"}, "scope_strict": true},
"doc block delete": {"block": ["index"], "note": "index (position) vs node (node id) are different"},
"doc +copy": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder"}, "block": ["parent-id"], "scope_strict": true, "note": "This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."},
"doc +list": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder"}, "block": ["parent-id"], "scope_strict": true, "note": "This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."},
"doc +move": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder"}, "block": ["parent-id"], "scope_strict": true, "note": "This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."},
"doc comment create": {"scoped_aliases": {"mentioned-open-conversation-ids": "mentioned-open-conversation-id", "open-conversation-id": "mentioned-open-conversation-id", "open-conversation-ids": "mentioned-open-conversation-id"}, "block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids"], "scope_strict": true, "note": "The target is a list of mentioned openConversationIds. Explicit open-conversation spellings preserve the list value; numeric groupId and role-free conversation spellings are rejected."},
"doc comment reply": {"scoped_aliases": {"mentioned-open-conversation-ids": "mentioned-open-conversation-id", "open-conversation-id": "mentioned-open-conversation-id", "open-conversation-ids": "mentioned-open-conversation-id"}, "block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids"], "scope_strict": true, "note": "The target is a list of mentioned openConversationIds. Explicit open-conversation spellings preserve the list value; numeric groupId and role-free conversation spellings are rejected."},
"doc comment update": {"scoped_aliases": {"mentioned-open-conversation-ids": "mentioned-open-conversation-id", "open-conversation-id": "mentioned-open-conversation-id", "open-conversation-ids": "mentioned-open-conversation-id"}, "block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids"], "scope_strict": true, "note": "The target is a list of mentioned openConversationIds. Explicit open-conversation spellings preserve the list value; numeric groupId and role-free conversation spellings are rejected."},
"doc +comment-create": {"block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids", "mentioned-open-conversation-id", "mentioned-open-conversation-ids", "open-conversation-id", "open-conversation-ids"], "note": "This shortcut has no group-mention flag or payload field. Group-conversation spellings are blocked instead of inventing unsupported capability; route to the stable doc comment command when group mentions are required."},
"doc +comment-reply": {"block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids", "mentioned-open-conversation-id", "mentioned-open-conversation-ids", "open-conversation-id", "open-conversation-ids"], "note": "This shortcut has no group-mention flag or payload field. Group-conversation spellings are blocked instead of inventing unsupported capability; route to the stable doc comment command when group mentions are required."},
"doc media insert": {"scoped_aliases": {"ref-block-id": "ref-block", "reference-block-id": "ref-block"}, "block": ["before-block-id", "parent-block", "parent-block-id"], "ambiguous": ["block-id"], "scope_strict": true, "note": "Media insertion supports a reference block but no parent-block role. --before-block-id additionally needs --where before; role-free --block-id is left ambiguous."},
"doc read": {"block": ["before-block-id", "parent-block-id", "ref-block-id", "reference-block-id"], "ambiguous": ["block-id"], "note": "A section read requires --scope section plus a start/end boundary. A role-free --block-id cannot be reduced to one flag without inventing the missing scope/boundary role."},
"doc export get": {"scoped_aliases": {"export-job-id": "job-id"}, "block": ["doc-id", "document-id", "file-id", "node", "node-id", "url"], "scope_strict": true, "note": "This command queries one export jobId. Document node identifiers are rejected; native hidden --task-id remains the command's reviewed add-only compatibility alias for --job-id."},
"doc import get": {"scoped_aliases": {"import-task-id": "task-id"}, "block": ["doc-id", "document-id", "file-id", "job-id", "node", "node-id", "url"], "scope_strict": true, "note": "This command queries one import taskId. Document node identifiers and export jobId spellings are different entities and are rejected."},
"doc +share-doc": {"block": ["doc", "doc-id", "document-id", "file-id", "id", "node", "node-id"], "note": "The real --url requires a shareable document link. Name-only normalization cannot turn a document nodeId into a URL, so identifier spellings are rejected with guidance to provide --url."},
"doc update": {"block": ["version", "version-no", "version-number"], "note": "--revision is an optimistic-concurrency revision, not a historical document version number. Version spellings must not reduce to --revision."},
"report outbox list": {"block": ["template-type"], "note": "type vs name are different fields"},
"chat group members add-bot": {"bind": {"id": "open_conversation_id"}},
"chat group members list-by-ids": {"bind": {"id": "open_conversation_id", "users": "open_dingtalk_ids"}, "block": ["user-id", "user-ids"], "note": "This command's --id carries openConversationId, while --users carries an openDingTalkId list."},
"chat group members remove-bot": {"bind": {"id": "open_conversation_id"}},
"chat +send-to-group": {"bind": {"group": "group_name"}, "note": "The real --group is a group-name search keyword on this shortcut, not an identifier."},
"chat group share-invite": {"scoped_aliases": {"source-conversation-id": "source", "target-conversation-id": "target"}, "block": ["group-id", "group-ids", "user", "user-id", "userid", "uid", "staff-id"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "note": "A role-free conversation identifier cannot choose between source and target; --receiver requires openDingTalkId and must not accept userId spellings."},
"chat message combine-forward": {"scoped_aliases": {"src-open-cid": "src-conversation-id", "dest-open-cid": "dest-conversation-id", "source-conversation-id": "src-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id"}, "block": ["group-id", "group-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "note": "Source and destination conversation roles are preserved; a role-free identifier is ambiguous."},
"chat message forward": {"scoped_aliases": {"src-open-cid": "src-conversation-id", "dest-open-cid": "dest-conversation-id", "source-conversation-id": "src-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id"}, "block": ["group-id", "group-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "note": "Source and destination conversation roles are preserved; a role-free identifier is ambiguous."},
"chat message forward-topic": {"scoped_aliases": {"src-open-conversation-id": "src-conversation-id", "dest-open-conversation-id": "dest-conversation-id", "source-conversation-id": "src-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id", "src-open-message-id": "src-msg-id", "source-message-id": "src-msg-id"}, "block": ["group-id", "group-ids", "msg-id", "message-id", "open-message-id"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "note": "Conversation and message source/destination roles are preserved; role-free identifiers are rejected."},
"chat +conversation-info": {"block": ["user", "user-id", "userid", "uid", "staff-id"], "note": "This shortcut accepts --open-dingtalk-id, not userId; use stable chat conversation-info when userId resolution is needed."},
"chat group create": {"block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a list and may contain mixed identifier domains."},
"chat +chat-set-admin": {"block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a mixed userId/openDingTalkId list."},
"chat +messages-read-status": {"block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a mixed userId/openDingTalkId list."},
"chat category create-smart": {"bind": {"members": "open_dingtalk_ids"}, "scoped_aliases": {"title": "name"}, "note": "The real --members is an openDingTalkId list; the reviewed title/name alias is exact to the category display name."},
"chat group audit-join-validation": {"ambiguous": ["user", "user-id", "userid", "uid", "staff-id"], "note": "A role-free user identifier cannot choose between the required --applicant and --inviter roles."},
"chat message reply": {"block": ["user", "user-id", "userid", "uid", "staff-id"], "note": "The required --ref-sender is a role-specific openDingTalkId and must not accept generic userId spellings."},
"chat message send-card": {"block": ["user", "user-id", "userid", "uid", "staff-id"], "note": "The real --receiver is a role-specific openDingTalkId and must not accept generic userId spellings."},
"chat +category-add-conversation": {"block": ["category-id"], "note": "The real --category-ids is a list; a singular category ID is not promoted automatically."},
"chat +category-list-conversations": {"block": ["category-ids"], "note": "The real --category-id is singular; list cardinality is not reduced automatically."},
"chat +category-remove-conversation": {"block": ["category-id"], "note": "The real --category-ids is a list; a singular category ID is not promoted automatically."},
"chat +chat-add-bot": {"bind": {"id": "open_conversation_id"}, "note": "The real --id is the group's openConversationId; robotCode and openBotId remain different domains."},
"chat +chat-audit-join": {"scoped_aliases": {"applicant-user-id": "applicant", "inviter-user-id": "inviter"}, "ambiguous": ["user", "user-id", "userid", "uid", "staff-id"], "scope_strict": true, "note": "A role-free user identifier cannot choose between applicant and inviter."},
"chat +chat-create": {"block": ["user-id", "open-dingtalk-id"], "note": "The real --users field is a list and may contain mixed identifier domains; a singular value is not promoted."},
"chat +chat-get-by-id": {"block": ["group", "conversation-id", "chat", "chat-id", "open-conversation-id", "conversation-ids", "open-conversation-ids", "group-name", "name", "id"], "note": "The real --group-id is numeric groupId; no CID or group-name spelling can be value-preservingly converted."},
"chat +chat-members-get": {"bind": {"id": "open_conversation_id", "users": "open_dingtalk_ids"}, "block": ["group", "group-name", "user-id", "user-ids"], "note": "The real --id is openConversationId and --users is an openDingTalkId list. The observed --group spelling carried a natural group name and is blocked; explicit CID spellings and --chat remain value-preserving aliases."},
"chat +chat-members-list": {"scoped_aliases": {"chat-id": "conversation-id", "id": "conversation-id"}, "block": ["query", "keyword", "group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "scope_strict": true, "note": "Native --chat/--open-conversation-id stay native; member filtering by query is unsupported and group-name resolution stays on --group/--chat-query."},
"chat +chat-mute-member": {"scoped_aliases": {"user-ids": "users", "open-dingtalk-ids": "users"}, "block": ["user", "user-id", "open-dingtalk-id"], "scope_strict": true, "note": "The target accepts a mixed identifier list; list spellings preserve values, but singular inputs are not promoted."},
"chat +chat-remove-bot": {"bind": {"id": "open_conversation_id"}, "note": "The real --id is openConversationId; --bot-id is separately governed by open_bot_id."},
"chat +chat-role-remove": {"block": ["role-ids"], "note": "The command removes one role ID; list cardinality is not reduced."},
"chat +chat-role-remove-user": {"scoped_aliases": {"user-id": "user", "open-dingtalk-id": "user"}, "block": ["role-id"], "scope_strict": true, "note": "The single --user accepts either identifier domain; --role-ids remains a list."},
"chat +chat-transfer-owner": {"scoped_aliases": {"user-id": "new-owner", "open-dingtalk-id": "new-owner"}, "scope_strict": true, "note": "The only user role is the new owner, and the target accepts either userId or openDingTalkId without changing the value."},
"chat +chat-update": {"scoped_aliases": {"conversation-id": "group", "open-conversation-id": "group", "chat-id": "group", "title": "name", "new-title": "name"}, "block": ["id", "group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "scope_strict": true, "note": "--group accepts a name or CID, so only explicit CID spellings are mapped; generic --id is blocked."},
"chat +conversation-set-top": {"scoped_aliases": {"open-conversation-id": "conversation-id", "chat-id": "conversation-id", "open-conversation-ids": "conversation-ids", "chat-ids": "conversation-ids"}, "block": ["group", "groups", "group-id", "group-ids", "top", "set-top"], "scope_strict": true, "note": "Singular/list cardinality stays explicit; top/set-top cannot be rewritten to the inverse --off switch."},
"chat +feed-group-query-item": {"scoped_aliases": {"open-conversation-ids": "conversation-ids", "chat-ids": "conversation-ids"}, "block": ["group", "groups", "group-id", "group-ids", "conversation-id", "open-conversation-id"], "scope_strict": true, "note": "The real field is an openConversationId list; group names and singular IDs are not converted."},
"chat +flag-list": {"scoped_aliases": {"limit": "size"}, "block": ["max", "max-results", "max-size", "count", "page", "page-size", "per-page"], "scope_strict": true, "note": "Only limit and size are reviewed as the same page bound; total-count and page-number spellings are not equivalent."},
"chat +messages-batch-recall-by-bot": {"block": ["msg-id", "message-id", "open-message-id", "msg-ids", "message-ids", "open-message-ids"], "note": "--keys carries processQueryKey values returned by bot sending; it is not an openMessageId field."},
"chat +messages-combine-forward": {"scoped_aliases": {"src-open-cid": "src-conversation-id", "src-open-conversation-id": "src-conversation-id", "source-conversation-id": "src-conversation-id", "dest-open-cid": "dest-conversation-id", "dest-open-conversation-id": "dest-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id"}, "block": ["group-id", "group-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "scope_strict": true, "note": "Source and destination conversation roles remain explicit; role-free CID spellings cannot choose a side."},
"chat +messages-forward": {"scoped_aliases": {"src-open-cid": "src-conversation-id", "src-open-conversation-id": "src-conversation-id", "source-conversation-id": "src-conversation-id", "dest-open-cid": "dest-conversation-id", "dest-open-conversation-id": "dest-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id", "src-open-message-id": "msg-id", "source-message-id": "msg-id"}, "block": ["group-id", "group-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "scope_strict": true, "note": "The message role is uniquely the source message, but source/destination conversation roles cannot be inferred from a generic CID."},
"chat +messages-forward-topic": {"scoped_aliases": {"src-open-conversation-id": "src-conversation-id", "source-conversation-id": "src-conversation-id", "dest-open-conversation-id": "dest-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id", "src-open-message-id": "src-msg-id", "source-message-id": "src-msg-id"}, "block": ["group-id", "group-ids", "msg-id", "message-id", "open-message-id", "msg-ids", "message-ids", "open-message-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "scope_strict": true, "note": "Source and destination conversation roles and the source-message role remain explicit; role-free message IDs and list cardinality are not inferred."},
"chat +messages-list": {"scoped_aliases": {"start": "time"}, "block": ["before", "before-time", "end", "direction", "page-all", "count", "max", "max-results", "max-size", "page-size"], "scope_strict": true, "note": "start preserves the same boundary value; before/direction require multi-parameter or value transforms and page-all requires iteration. Native --conversation-id/--id/--size remain native."},
"chat +messages-recall-by-bot": {"block": ["msg-id", "message-id", "open-message-id", "msg-ids", "message-ids", "open-message-ids"], "note": "--keys carries processQueryKey values, not openMessageId values."},
"chat +messages-reply": {"scoped_aliases": {"msg-id": "ref-msg-id", "open-message-id": "ref-msg-id"}, "block": ["group", "msg-ids", "message-ids", "open-message-ids"], "scope_strict": true, "note": "The observed --group spelling carried a natural group name and is blocked. The only message role is the referenced message; plural IDs are not accepted, while --chat remains a CID alias and native --message-id stays native."},
"chat +messages-resource-download": {"block": ["download-dir"], "note": "--output may be a file or directory under workspace safety rules; a download directory cannot be assumed equivalent."},
"doc +create": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder"}, "block": ["content-file", "parent-id"], "scope_strict": true, "note": "--content-format is value-preservingly normalized to --doc-format. A raw --content-file path cannot become --content without adding the required @file transform, so it is blocked with guidance to use @relative-path or stable doc create."},
"doc +create-from-template": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder"}, "block": ["parent-id"], "scope_strict": true, "note": "This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."},
"doc +import": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder"}, "block": ["parent-id"], "scope_strict": true, "note": "This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."},
"doc +update": {"scoped_aliases": {"mode": "command", "doc-id": "node", "document-id": "node", "file-id": "node", "node-id": "node", "url": "node"}, "block": ["content-file"], "scope_strict": true, "note": "--mode append/overwrite is the same operation selector subset as --command and preserves its value. --content-file is blocked because +update requires @relative-path or stdin and central aliases cannot read/transform a file value."},
"doc +inspect": {"scoped_aliases": {"include-versions": "include-history"}, "block": ["include", "include-info"], "scope_strict": true, "note": "Historical versions and history are the same optional section on this exact shortcut. Generic --include needs value-dependent flag expansion, while base document info is always returned, so those spellings are rejected with precise guidance."},
"doc +fetch": {"scoped_aliases": {"start-block": "start-block-id", "end-block": "end-block-id"}, "ambiguous": ["block-id"], "scope_strict": true, "note": "Start/end block roles are preserved. A role-free --block-id cannot choose a range/section boundary and must stop before execution."},
"doc +media-download": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "This command has both document-node and attachment-resource roles. Strong document spellings map to --node; --file-id and --url cannot safely choose between document and media identities."},
"doc +media-insert": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "This command has both document-node and local-media roles. Strong document spellings map to --node; --file-id and --url cannot safely choose a role."},
"doc +media-list": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "The command lists media inside one document, so only strong document spellings map to --node. File and URL spellings remain role-ambiguous."},
"doc +media-preview": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "This command has both document-node and attachment-resource roles. Strong document spellings map to --node; --file-id and --url cannot safely choose a role."},
"doc +resource-delete": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "This command removes a document resource while targeting the document by --node. File and URL spellings do not uniquely identify that document role."},
"doc +resource-download": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "This command downloads a document resource while targeting the document by --node. File and URL spellings do not uniquely identify that document role."},
"doc +resource-update": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "This command has document-node, local-file, and HTTPS image URL roles. Only strong document spellings map to --node; --file-id and --url must stop as ambiguous."},
"doc +share": {"block": ["doc", "doc-id", "document-id", "file-id", "id", "node", "node-id"], "note": "The real --url requires a shareable document link. Name-only normalization cannot turn a node identifier into a URL, so identifier spellings are rejected with guidance to provide --url."},
"doc +grant-and-share": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "file-id": "node", "node-id": "node"}, "scope_strict": true, "note": "This workflow has two different real URL roles: --node selects the document for access control and --url is the shareable link sent to recipients. Explicit document-ID spellings map only to --node; --url remains native."}
},
"validation_fixture": {
"cases": [
{"command": "oa +search-forms", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 28},
{"command": "aitable +list-tables", "emitted": "base-id", "expect": "base", "via": "concept:base_id+morph", "occ": 26},
{"command": "mail +find-mail-user", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 18},
{"command": "aitable +field-get", "emitted": "base", "expect": "base-id", "via": "concept:base_id+morph", "occ": 8},
{"command": "aitable +record-query", "emitted": "base", "expect": "base-id", "via": "concept:base_id+morph", "occ": 8},
{"command": "aitable +table-get", "emitted": "base", "expect": "base-id", "via": "concept:base_id+morph", "occ": 8},
{"command": "doc block update", "emitted": "content", "expect": "text", "via": "concept:content_text", "occ": 6},
{"command": "contact +resolve-dept", "emitted": "query", "expect": "name", "via": "concept:search_query+bind", "occ": 4},
{"command": "devdoc article search", "emitted": "limit", "expect": "size", "via": "concept:pagination_size", "occ": 2},
{"command": "devdoc article search", "emitted": "page-size", "expect": "size", "via": "concept:pagination_size", "occ": 2},
{"command": "devdoc article search", "emitted": "current-page", "expect": "page", "via": "concept:page_number", "occ": 2},
{"command": "mail message search", "emitted": "subject", "expect": "query", "via": "override:scoped_strict", "occ": 4},
{"command": "aitable +record-share-url", "emitted": "base", "expect": "base-id", "via": "concept:base_id+morph", "occ": 3},
{"command": "aitable record query", "emitted": "max-results", "expect": "limit", "via": "concept:pagination_size", "occ": 2},
{"command": "calendar event list", "emitted": "date", "expect": "start", "via": "override:scoped(reviewed+payload)", "occ": 2},
{"command": "calendar event list", "emitted": "start-time", "expect": "start", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "min-time", "expect": "start", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "time-min", "expect": "start", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "end-time", "expect": "end", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "time-max", "expect": "end", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "max-results", "expect": "limit", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "next-cursor", "expect": "cursor", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "calendar", "expect": "calendar-id", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "chat message list", "emitted": "max-results", "expect": "limit", "via": "concept:pagination_size", "occ": 2},
{"command": "chat message list-by-sender", "emitted": "time", "expect": "did-you-mean:blocked", "via": "guard:time-format-boundary", "occ": 2},
{"command": "doc +template-search", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 2},
{"command": "doc block insert", "emitted": "content", "expect": "text", "via": "concept:content_text", "occ": 2},
{"command": "drive list", "emitted": "folder-id", "expect": "folder", "via": "concept:folder_id+morph", "occ": 2},
{"command": "mail thread list", "emitted": "max-results", "expect": "limit", "via": "concept:pagination_size", "occ": 2},
{"command": "mail user search", "emitted": "query", "expect": "keyword", "via": "concept:search_query", "occ": 2},
{"command": "oa +list-executed", "emitted": "take", "expect": "limit", "via": "concept:pagination_size", "occ": 2},
{"command": "oa approval search-forms", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 2},
{"command": "report list", "emitted": "from-date", "expect": "start", "via": "concept:time_start", "occ": 2},
{"command": "aitable +base-search", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 1},
{"command": "contact +search-user", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 1},
{"command": "chat group rename", "emitted": "group", "expect": "id", "via": "override:bind(open_conversation_id)", "occ": 31},
{"command": "ding +receiver-status", "emitted": "id", "expect": "ding-id", "via": "override:scoped(ding_id)", "occ": 24},
{"command": "chat group members", "emitted": "group", "expect": "id", "via": "override:bind(open_conversation_id)", "occ": 8},
{"command": "contact +list-sub-depts", "emitted": "dept-id", "expect": "dept", "via": "concept:dept_id+morph", "occ": 4},
{"command": "contact +list-sub-depts", "emitted": "name", "expect": "did-you-mean:blocked", "via": "guard:name-vs-id", "occ": 2},
{"command": "contact +list-sub-depts", "emitted": "query", "expect": "did-you-mean:blocked", "via": "guard:query-vs-id", "occ": 2},
{"command": "contact user profile get", "emitted": "user-id", "expect": "staff-id", "via": "concept:user_id", "occ": 2},
{"command": "contact user profile get", "emitted": "id", "expect": "staff-id", "via": "override:scoped", "occ": 2},
{"command": "contact user profile get", "emitted": "ids", "expect": "staff-id", "via": "override:scoped", "occ": 2},
{"command": "chat message send-by-bot", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list", "occ": 4},
{"command": "chat message send-by-bot", "emitted": "to-user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list", "occ": 1},
{"command": "dev app get", "emitted": "app-id", "expect": "unified-app-id", "via": "concept:app_id", "occ": 5},
{"command": "chat message list-all", "emitted": "from", "expect": "start", "via": "concept:time_start", "occ": 2},
{"command": "chat message list-all", "emitted": "start-time", "expect": "start", "via": "concept:time_start", "occ": 2},
{"command": "chat message search-advanced", "emitted": "group", "expect": "conversation-ids", "via": "native:reviewed-single-to-list", "occ": 4},
{"command": "chat message send", "emitted": "to-user", "expect": "user", "via": "override:scoped(reviewed+payload)", "occ": 4},
{"command": "contact +dept-members", "emitted": "name", "expect": "dept", "via": "override:scoped(reviewed)", "occ": 2},
{"command": "contact +dept-members", "emitted": "query", "expect": "dept", "via": "concept:search_query+bind", "occ": 2},
{"command": "contact dept list-children", "emitted": "parent-id", "expect": "dept", "via": "concept:dept_id", "occ": 2},
{"command": "contact dept list-children", "emitted": "parent", "expect": "dept", "via": "concept:dept_id", "occ": 2},
{"command": "ding message receiver-status", "emitted": "id", "expect": "ding-id", "via": "override:scoped(ding_id)", "occ": 2},
{"command": "ding message receiver-status", "emitted": "open-ding-id", "expect": "ding-id", "via": "concept:ding_id", "occ": 2},
{"command": "chat group members add", "emitted": "group", "expect": "id", "via": "override:bind(open_conversation_id)", "occ": 3},
{"command": "attendance +check-result", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "concept:user_ids+exclude", "occ": 2},
{"command": "attendance check result", "emitted": "user-ids", "expect": "users", "via": "concept:user_ids", "occ": 2},
{"command": "chat group members remove", "emitted": "group", "expect": "id", "via": "override:bind(open_conversation_id)", "occ": 2},
{"command": "chat group set-admin", "emitted": "user-id", "expect": "user", "via": "native:reviewed-compatibility-alias", "occ": 2},
{"command": "ding message send", "emitted": "robot", "expect": "robot-code", "via": "concept:robot_code", "occ": 2},
{"command": "doc +export-get", "emitted": "node", "expect": "did-you-mean:blocked", "via": "override:block", "occ": 2},
{"command": "doc block delete", "emitted": "index", "expect": "did-you-mean:blocked", "via": "override:block", "occ": 2},
{"command": "doc block insert", "emitted": "before-block-id", "expect": "did-you-mean:blocked", "via": "guard:requires-multi-parameter-transform", "occ": 2},
{"command": "drive info", "emitted": "workspace", "expect": "space-id", "via": "concept:space_id", "occ": 2},
{"command": "mail folder update", "emitted": "folder-id", "expect": "id", "via": "override:bind(folder_id)", "occ": 2},
{"command": "report outbox list", "emitted": "template-type", "expect": "did-you-mean:blocked", "via": "override:block", "occ": 2},
{"command": "chat +group-members", "emitted": "group-name", "expect": "group", "via": "concept:group_name+bind"},
{"command": "chat group get-by-group-id", "emitted": "conversation-id", "expect": "did-you-mean:blocked", "via": "guard:open-conversation-id-vs-group-id"},
{"command": "chat group get-by-group-id", "emitted": "id", "expect": "did-you-mean:blocked", "via": "guard:generic-id-vs-group-id"},
{"command": "chat group rename", "emitted": "conversation-id", "expect": "id", "via": "concept:open_conversation_id+bind"},
{"command": "chat group rename", "emitted": "group-id", "expect": "did-you-mean:blocked", "via": "guard:group-id-vs-open-conversation-id"},
{"command": "chat message send", "emitted": "conversation-id", "expect": "group", "via": "concept:open_conversation_id"},
{"command": "chat message add-emoji", "emitted": "open-conversation-id", "expect": "conversation-id", "via": "override:scoped"},
{"command": "chat message add-emoji", "emitted": "group-id", "expect": "did-you-mean:blocked", "via": "guard:group-id-vs-open-conversation-id"},
{"command": "chat +group-members", "emitted": "conversation-id", "expect": "did-you-mean:blocked", "via": "guard:group-name-vs-open-conversation-id"},
{"command": "chat +send-to-group", "emitted": "group-name", "expect": "group", "via": "concept:group_name+bind"},
{"command": "chat message search-advanced", "emitted": "open-conversation-ids", "expect": "conversation-ids", "via": "concept:open_conversation_ids"},
{"command": "chat message search-advanced", "emitted": "group-ids", "expect": "did-you-mean:blocked", "via": "guard:group-id-list-vs-open-conversation-id-list"},
{"command": "chat message recall", "emitted": "message-id", "expect": "msg-id", "via": "concept:open_message_id"},
{"command": "chat message add-favorite", "emitted": "msg-id", "expect": "open-message-id", "via": "concept:open_message_id"},
{"command": "chat message list-by-ids", "emitted": "message-ids", "expect": "msg-ids", "via": "concept:open_message_ids"},
{"command": "chat message list-by-ids", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat message reply", "emitted": "ref-message-id", "expect": "ref-msg-id", "via": "concept:referenced_open_message_id"},
{"command": "chat message reply", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:message-role"},
{"command": "chat message forward-topic", "emitted": "src-open-message-id", "expect": "src-msg-id", "via": "override:scoped-role"},
{"command": "chat message forward-topic", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:message-role"},
{"command": "chat message combine-forward", "emitted": "src-open-cid", "expect": "src-conversation-id", "via": "override:scoped-role"},
{"command": "chat message forward-topic", "emitted": "dest-open-conversation-id", "expect": "dest-conversation-id", "via": "override:scoped-role"},
{"command": "chat message forward", "emitted": "conversation-id", "expect": "did-you-mean:ambiguous", "via": "guard:source-vs-destination-role"},
{"command": "chat group share-invite", "emitted": "conversation-id", "expect": "did-you-mean:ambiguous", "via": "guard:source-vs-target-role"},
{"command": "chat message send", "emitted": "user-id", "expect": "user", "via": "concept:user_id"},
{"command": "chat message list", "emitted": "user-id", "expect": "user", "via": "concept:user_id"},
{"command": "chat +messages-list-direct", "emitted": "user-id", "expect": "user", "via": "concept:user_id"},
{"command": "attendance +check-result", "emitted": "user-ids", "expect": "users", "via": "concept:user_ids"},
{"command": "contact +list-sub-depts", "emitted": "parent-id", "expect": "dept", "via": "concept:dept_id"},
{"command": "chat +conversation-info", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:user-id-vs-open-dingtalk-id"},
{"command": "chat group members remove", "emitted": "user-ids", "expect": "users", "via": "concept:user_ids"},
{"command": "chat group members remove", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat group create", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +chat-set-admin", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +messages-read-status", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat group members list-by-ids", "emitted": "open-dingtalk-ids", "expect": "users", "via": "concept:open_dingtalk_ids+bind"},
{"command": "chat group members remove-bot", "emitted": "open-bot-id", "expect": "bot-id", "via": "concept:open_bot_id"},
{"command": "chat group members remove-bot", "emitted": "robot-code", "expect": "did-you-mean:blocked", "via": "guard:robot-code-vs-open-bot-id"},
{"command": "chat group members add-bot", "emitted": "robot", "expect": "robot-code", "via": "concept:robot_code"},
{"command": "chat +bot-find", "emitted": "name", "expect": "query", "via": "override:scoped"},
{"command": "chat bot find", "emitted": "name", "expect": "query", "via": "override:scoped"},
{"command": "chat +bot-search", "emitted": "query", "expect": "name", "via": "override:scoped"},
{"command": "chat bot search", "emitted": "query", "expect": "name", "via": "override:scoped"},
{"command": "chat message list-favorites", "emitted": "limit", "expect": "size", "via": "override:scoped"},
{"command": "chat bot search", "emitted": "current-page", "expect": "page", "via": "override:scoped"},
{"command": "chat bot search", "emitted": "cursor", "expect": "did-you-mean:blocked", "via": "guard:page-number-vs-cursor"},
{"command": "chat message list-unread-conversations", "emitted": "limit", "expect": "count", "via": "override:scoped"},
{"command": "chat +messages-list-unread-conversations", "emitted": "size", "expect": "count", "via": "override:scoped"},
{"command": "chat message list", "emitted": "start", "expect": "time", "via": "override:scoped"},
{"command": "chat message list", "emitted": "end", "expect": "did-you-mean:blocked", "via": "guard:single-time-vs-range"},
{"command": "chat message list-all", "emitted": "time", "expect": "did-you-mean:blocked", "via": "guard:time-range-required"},
{"command": "chat message list-by-sender", "emitted": "user-id", "expect": "sender-user-id", "via": "override:scoped-role"},
{"command": "chat message list-by-sender", "emitted": "open-dingtalk-id", "expect": "sender-open-dingtalk-id", "via": "override:scoped-role"},
{"command": "chat category create-smart", "emitted": "title", "expect": "name", "via": "override:scoped"},
{"command": "chat category create", "emitted": "name", "expect": "title", "via": "override:scoped"},
{"command": "chat message send", "emitted": "file", "expect": "file-path", "via": "override:scoped"},
{"command": "chat category add-conv", "emitted": "category-id", "expect": "did-you-mean:blocked", "via": "override:block-cardinality"},
{"command": "chat category rename", "emitted": "category-ids", "expect": "did-you-mean:blocked", "via": "override:block-cardinality"},
{"command": "chat group-role set-user", "emitted": "role-id", "expect": "did-you-mean:blocked", "via": "override:block-cardinality"},
{"command": "chat group-role update", "emitted": "role-ids", "expect": "did-you-mean:blocked", "via": "override:block-cardinality"},
{"command": "chat message send-by-webhook", "emitted": "at-user-ids", "expect": "at-users", "via": "override:scoped-role"},
{"command": "chat message send-by-bot", "emitted": "at-users", "expect": "at-user-ids", "via": "override:scoped-role"},
{"command": "chat message send-by-bot", "emitted": "at-ids", "expect": "did-you-mean:ambiguous", "via": "guard:user-id-vs-open-dingtalk-id"},
{"command": "chat +bot-search", "emitted": "current-page", "expect": "page", "via": "override:scoped"},
{"command": "chat +category-create", "emitted": "name", "expect": "title", "via": "override:scoped"},
{"command": "chat +category-rename", "emitted": "name", "expect": "title", "via": "override:scoped"},
{"command": "chat +messages-list-direct", "emitted": "start", "expect": "time", "via": "override:scoped"},
{"command": "chat +messages-list-unread-conversations", "emitted": "limit", "expect": "count", "via": "override:scoped"},
{"command": "chat +messages-send-by-webhook", "emitted": "at-user-ids", "expect": "at-users", "via": "override:scoped-role"},
{"command": "chat +unread-chats", "emitted": "limit", "expect": "count", "via": "override:scoped"},
{"command": "chat +unread-chats", "emitted": "size", "expect": "count", "via": "override:scoped"},
{"command": "chat category rename", "emitted": "name", "expect": "title", "via": "override:scoped"},
{"command": "chat message list-unread-conversations", "emitted": "size", "expect": "count", "via": "override:scoped"},
{"command": "doc +comment-create", "emitted": "node-id", "expect": "node", "via": "concept:doc_node_id"},
{"command": "doc +doc-append", "emitted": "node", "expect": "doc", "via": "concept:doc_node_id"},
{"command": "doc +find-doc", "emitted": "keyword", "expect": "query", "via": "concept:search_query"},
{"command": "doc +search", "emitted": "q", "expect": "query", "via": "concept:search_query"},
{"command": "doc +comment-list", "emitted": "max-results", "expect": "limit", "via": "concept:pagination_size"},
{"command": "doc +list", "emitted": "page-token", "expect": "cursor", "via": "concept:page_cursor"},
{"command": "doc +copy", "emitted": "workspace-id", "expect": "workspace", "via": "concept:space_id"},
{"command": "doc +copy", "emitted": "parent-folder-id", "expect": "folder", "via": "override:scoped-doc-folder"},
{"command": "doc +copy", "emitted": "parent-id", "expect": "did-you-mean:blocked", "via": "guard:doc-folder-value-domain"},
{"command": "doc +comment-reply", "emitted": "comment-id", "expect": "comment-key", "via": "concept:doc_comment_key"},
{"command": "doc comment reply", "emitted": "mentioned-open-conversation-ids", "expect": "mentioned-open-conversation-id", "via": "override:scoped-role-list"},
{"command": "doc comment reply", "emitted": "group-id", "expect": "did-you-mean:blocked", "via": "guard:numeric-group-id-vs-open-conversation-id"},
{"command": "doc +comment-create", "emitted": "mentioned-open-conversation-id", "expect": "did-you-mean:blocked", "via": "guard:shortcut-missing-capability"},
{"command": "doc block insert", "emitted": "parent-block-id", "expect": "parent-block", "via": "override:scoped-block-role"},
{"command": "doc block insert", "emitted": "block-id", "expect": "did-you-mean:ambiguous", "via": "guard:parent-vs-reference-block-role"},
{"command": "doc media insert", "emitted": "before-block-id", "expect": "did-you-mean:blocked", "via": "guard:requires-ref-block-plus-where"},
{"command": "doc read", "emitted": "block-id", "expect": "did-you-mean:ambiguous", "via": "guard:requires-scope-and-boundary-role"},
{"command": "doc export get", "emitted": "node", "expect": "did-you-mean:blocked", "via": "guard:document-node-vs-export-job"},
{"command": "doc import get", "emitted": "job-id", "expect": "did-you-mean:blocked", "via": "guard:export-job-vs-import-task"},
{"command": "doc +version-revert", "emitted": "version-number", "expect": "version", "via": "concept:doc_version_number"},
{"command": "doc +version-revert", "emitted": "revision", "expect": "did-you-mean:blocked", "via": "concept:doc_version_number+exclude"},
{"command": "doc update", "emitted": "version", "expect": "did-you-mean:blocked", "via": "guard:historical-version-vs-edit-revision"},
{"command": "doc +share-doc", "emitted": "node-id", "expect": "did-you-mean:blocked", "via": "guard:node-id-needs-url-conversion"},
{"command": "doc +comment-create", "emitted": "body", "expect": "content", "via": "concept:content_text"},
{"command": "doc +doc-append", "emitted": "content", "expect": "text", "via": "concept:content_text"},
{"command": "doc +export-submit", "emitted": "doc-id", "expect": "node", "via": "concept:doc_node_id"},
{"command": "doc +move", "emitted": "parent-folder-id", "expect": "folder", "via": "override:scoped-doc-folder"},
{"command": "doc +template-list", "emitted": "next-token", "expect": "cursor", "via": "concept:page_cursor"},
{"command": "doc +version-list", "emitted": "page-size", "expect": "limit", "via": "concept:pagination_size"},
{"command": "doc +version-save", "emitted": "file-id", "expect": "node", "via": "concept:doc_node_id"},
{"command": "doc comment create", "emitted": "text", "expect": "content", "via": "concept:content_text"},
{"command": "doc comment create-inline", "emitted": "body", "expect": "content", "via": "concept:content_text"},
{"command": "doc comment delete", "emitted": "comment-id", "expect": "comment-key", "via": "concept:doc_comment_key"},
{"command": "doc comment update", "emitted": "comment-id", "expect": "comment-key", "via": "concept:doc_comment_key"},
{"command": "doc version revert", "emitted": "version-no", "expect": "version", "via": "concept:doc_version_number"},
{"command": "chat +chat-messages", "emitted": "chat", "expect": "group", "via": "override:scoped-eval"},
{"command": "chat +search-msg", "emitted": "chat", "expect": "group", "via": "override:scoped-eval"},
{"command": "chat +chat-update", "emitted": "chat-id", "expect": "group", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-update", "emitted": "conversation-id", "expect": "group", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-update", "emitted": "open-conversation-id", "expect": "group", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-update", "emitted": "id", "expect": "did-you-mean:blocked", "via": "guard:generic-id-value-domain"},
{"command": "chat +chat-update", "emitted": "title", "expect": "name", "via": "override:scoped-group-title"},
{"command": "chat +chat-update", "emitted": "new-title", "expect": "name", "via": "override:scoped-group-title"},
{"command": "chat +flag-list", "emitted": "limit", "expect": "size", "via": "override:scoped-page-bound"},
{"command": "chat +flag-list", "emitted": "max", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +flag-list", "emitted": "max-results", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +flag-list", "emitted": "max-size", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +chat-members-list", "emitted": "chat-id", "expect": "conversation-id", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-members-list", "emitted": "id", "expect": "conversation-id", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-members-list", "emitted": "query", "expect": "did-you-mean:blocked", "via": "guard:unsupported-member-filter"},
{"command": "chat +conversation-set-top", "emitted": "open-conversation-id", "expect": "conversation-id", "via": "override:scoped-explicit-cid"},
{"command": "chat +conversation-set-top", "emitted": "chat-ids", "expect": "conversation-ids", "via": "override:scoped-explicit-cid-list"},
{"command": "chat +conversation-set-top", "emitted": "groups", "expect": "did-you-mean:blocked", "via": "guard:group-name-or-list-ambiguity"},
{"command": "chat +conversation-set-top", "emitted": "top", "expect": "did-you-mean:blocked", "via": "guard:inverse-boolean-semantics"},
{"command": "chat +chat-members-get", "emitted": "conversation-id", "expect": "id", "via": "concept:open_conversation_id+bind"},
{"command": "chat +chat-members-get", "emitted": "open-dingtalk-ids", "expect": "users", "via": "concept:open_dingtalk_ids+bind"},
{"command": "chat +chat-members-get", "emitted": "group", "expect": "did-you-mean:blocked", "via": "guard:group-name-vs-open-conversation-id"},
{"command": "chat +chat-members-get", "emitted": "chat", "expect": "id", "via": "concept:open_conversation_id+bind"},
{"command": "chat +chat-get-by-id", "emitted": "group", "expect": "did-you-mean:blocked", "via": "guard:open-conversation-id-vs-numeric-group-id"},
{"command": "chat +messages-list", "emitted": "start", "expect": "time", "via": "override:scoped-time-boundary"},
{"command": "chat +messages-list", "emitted": "count", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +messages-list", "emitted": "max-results", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +messages-list", "emitted": "page-all", "expect": "did-you-mean:blocked", "via": "guard:requires-pagination-loop"},
{"command": "chat +messages-reply", "emitted": "msg-id", "expect": "ref-msg-id", "via": "override:scoped-reference-message"},
{"command": "chat +messages-reply", "emitted": "group", "expect": "did-you-mean:blocked", "via": "guard:group-name-vs-open-conversation-id"},
{"command": "chat +messages-reply", "emitted": "chat", "expect": "conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +flag-cancel", "emitted": "group", "expect": "conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +flag-cancel", "emitted": "chat", "expect": "conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +flag-create", "emitted": "group", "expect": "conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +chat-add-bot", "emitted": "conversation-id", "expect": "id", "via": "concept:open_conversation_id+bind"},
{"command": "chat +chat-add-bot", "emitted": "robot", "expect": "robot-code", "via": "concept:robot_code"},
{"command": "chat +chat-audit-join", "emitted": "applicant-user-id", "expect": "applicant", "via": "override:scoped-user-role"},
{"command": "chat +chat-audit-join", "emitted": "user-id", "expect": "did-you-mean:ambiguous", "via": "guard:applicant-vs-inviter-role"},
{"command": "chat +chat-create", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +chat-mute-member", "emitted": "user-ids", "expect": "users", "via": "override:scoped-mixed-id-list"},
{"command": "chat +chat-mute-member", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +chat-remove-bot", "emitted": "open-bot-id", "expect": "bot-id", "via": "concept:open_bot_id"},
{"command": "chat +chat-role-remove", "emitted": "role-ids", "expect": "did-you-mean:blocked", "via": "guard:list-vs-single"},
{"command": "chat +chat-role-remove-user", "emitted": "open-dingtalk-id", "expect": "user", "via": "override:scoped-mixed-id"},
{"command": "chat +chat-transfer-owner", "emitted": "user-id", "expect": "new-owner", "via": "override:scoped-owner-role"},
{"command": "chat +feed-group-query-item", "emitted": "chat-ids", "expect": "conversation-ids", "via": "override:scoped-explicit-cid-list"},
{"command": "chat +feed-group-query-item", "emitted": "conversation-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +messages-batch-recall-by-bot", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:process-query-key-vs-open-message-id"},
{"command": "chat +messages-combine-forward", "emitted": "src-open-cid", "expect": "src-conversation-id", "via": "override:scoped-source-role"},
{"command": "chat +messages-combine-forward", "emitted": "conversation-id", "expect": "did-you-mean:ambiguous", "via": "guard:source-vs-destination-role"},
{"command": "chat +messages-forward", "emitted": "source-message-id", "expect": "msg-id", "via": "override:scoped-source-role"},
{"command": "chat +messages-forward", "emitted": "conversation-id", "expect": "did-you-mean:ambiguous", "via": "guard:source-vs-destination-role"},
{"command": "chat +messages-forward-topic", "emitted": "src-open-message-id", "expect": "src-msg-id", "via": "override:scoped-source-role"},
{"command": "chat +messages-forward-topic", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:message-source-role"},
{"command": "chat +messages-recall-by-bot", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:process-query-key-vs-open-message-id"},
{"command": "chat +messages-resource-download", "emitted": "conversation-id", "expect": "open-conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +messages-resource-download", "emitted": "download-dir", "expect": "did-you-mean:blocked", "via": "guard:output-file-or-directory-contract"},
{"command": "chat +messages-set-pin", "emitted": "conversation-id", "expect": "open-conversation-id", "via": "concept:open_conversation_id"},
{"command": "doc +create", "emitted": "content-format", "expect": "doc-format", "via": "concept:doc_content_format"},
{"command": "doc +create", "emitted": "content-file", "expect": "did-you-mean:blocked", "via": "guard:requires-file-read-transform"},
{"command": "doc +inspect", "emitted": "include-versions", "expect": "include-history", "via": "override:scoped-section"},
{"command": "doc +inspect", "emitted": "include", "expect": "did-you-mean:blocked", "via": "guard:requires-value-dependent-flag-expansion"},
{"command": "doc +inspect", "emitted": "include-info", "expect": "did-you-mean:blocked", "via": "guard:base-info-always-returned"},
{"command": "doc +update", "emitted": "mode", "expect": "command", "via": "override:scoped-operation"},
{"command": "doc +update", "emitted": "revision", "expect": "expected-revision", "via": "concept:doc_edit_revision"},
{"command": "doc +update", "emitted": "version", "expect": "did-you-mean:blocked", "via": "guard:historical-version-vs-edit-revision"},
{"command": "doc +fetch", "emitted": "start-block", "expect": "start-block-id", "via": "override:scoped-boundary-role"},
{"command": "doc +fetch", "emitted": "block-id", "expect": "did-you-mean:ambiguous", "via": "guard:start-vs-end-boundary-role"},
{"command": "doc +access-grant", "emitted": "doc-id", "expect": "node", "via": "concept:doc_node_id"},
{"command": "doc +history-revert", "emitted": "version-number", "expect": "version", "via": "concept:doc_version_number"},
{"command": "doc +create-from-template", "emitted": "keyword", "expect": "query", "via": "concept:search_query"},
{"command": "doc +create-from-template", "emitted": "workspace-id", "expect": "workspace", "via": "concept:space_id"},
{"command": "doc +create-from-template", "emitted": "parent-folder-id", "expect": "folder", "via": "override:scoped-doc-folder"},
{"command": "doc +media-download", "emitted": "file-id", "expect": "did-you-mean:ambiguous", "via": "guard:document-node-vs-attachment-resource-role"},
{"command": "doc +resource-update", "emitted": "url", "expect": "did-you-mean:ambiguous", "via": "guard:document-url-vs-image-url-role"},
{"command": "doc +share", "emitted": "node-id", "expect": "did-you-mean:blocked", "via": "guard:node-id-needs-url-conversion"}
]
}
}
@@ -0,0 +1,158 @@
# DWS Drive 产品 CLI 参数幻觉分析
## 1. 结论摘要
本轮以线上 `main` 提交 `fd24619437afcb92638d6a71e0bfd9254815fe06`(2026-08-11 14:08:11 +0800)为冻结基线,从该提交重新构建二进制,并使用同一官方命令树对 Drive 的真实 Help、运行时组装 Schema、仓库内置 Skill、命令实现和正式参数概念表进行对账。分析未使用历史 badcase、`dws-eval`、历史工作簿、固定 Catalog、用户自定义 Shortcut 或插件。
Drive 当前共有 **41 个 Agent-visible 工具**,其中 **7 个仓库内置 Shortcut**;全部 41 个命令都有业务参数,共出现 **154 次业务参数**、形成 **52 个不同公开 flag 名**。初版盘点把 `quiet` 按常见全局输出参数排除了,但 `drive list --quiet` 实际是该 leaf 的本地业务/输出行为参数,用于关闭递归进度输出,因此本版已补回。逐命令对账后,真实 Help 与运行时 Schema 的公开业务 flag 集合差异为 **0**,说明本轮参数问题不是 Schema 快照过期,而主要来自同一产品内部的命名、值域、业务角色和可发现性差异。
分析聚合为 **7 类参数问题**,在 Excel 中形成 **89 条“问题—命令”明细**,覆盖全部 41 个有业务参数的命令。最需要优先处理的是:
- `--node`、`--folder`、`--space-id`、`--workspace` 和回收站 `--id` 都像资源标识符,但分别属于节点、父目录、数字存储空间、知识库工作区和回收项,不能全局互换;
- Drive Skill 明确区分数字型 `dentryId` 与 `dentryUuid/fileId`:公开 `--node` 接收的是后者,因此 `dentry-uuid → node` 可以归一,而 `dentry-id → node` 必须在相关命令中拦截;
- copy/move/upload/commit 等写操作同时存在源对象与目标位置,参数实体相近但业务角色相反;
- permission 命令同时出现用户列表、授权角色、筛选角色、新所有者和保留角色,单复数与操作角色都必须区分;
- 文件上传、下载和版本工作流中的本地路径、远端名称、总字节数、分片大小、版本号和上传会话 ID 不能因为名称相近而互转;
- 仓库已有大量隐藏兼容 flag,但 `drive permission list --page-size` 和 `drive upload --file-path` 存在“Cobra 接受、最终实现未读取”的问题,不能把解析成功误判为业务等价。
冻结正式别名表源码中有 2 个 Drive override,生成结果实际触达 **3 个 Drive 命令、4 条 alias、1 条 block、2 条 ambiguous**。收敛后的候选草稿扩展到 **41 个 Drive 命令、376 条 alias、206 条 block、39 条 ambiguous**;相对冻结正式表新增 8 个 Drive concept、扩展 6 个既有 concept 的 Drive 命令范围、新增 36 个 Drive override,并修正 1 条已不符合业务语义的 Drive validation fixture。文件名、显示名、下载输出路径、排序字段、上传会话 ID 和新所有者这 6 类只在少量命令中成立,因此改为精确 command override,不再创建产品级 concept。所有非 Drive concept、override、保护规则和 fixture 均保持不变。
收敛后的候选已在隔离副本通过生成、构建、4 组代表性 alias/canonical 最终 dry-run payload 等价、2 组保护行为以及 `internal/cli`、`internal/pipeline` 回归。因为 fixture 从 alias 改为 block,正式落地还必须同步删除 `param_alias_payload_equivalence_test.go` 中已经没有 active alias 对应的 `drive info` complete-command 模板;候选 JSON 单独替换会被 stale-template 门禁拦住。候选仍是待评审草稿,不会直接替换当前工作区的正式 `internal/cli/param_concepts.json`。
Skill 审核也在本次复查中收紧:仓库内置 Drive Skill 只显式提到 41 个可见工具中的 **25 个**,另有 **16 个**没有命令级说明;共有 **13 个真实公开 flag** 未在 Skill 中出现。已提及的命令示例没有发现错误 flag,并不等于 Skill 对全部 Drive 命令和参数完整覆盖。
## 2. 参数问题
### 2.1 节点、目录、存储空间、知识库与回收项标识符混杂
Drive 最常见的参数是 `--node`,用于文件或目录节点;`--folder` 表示父目录或目标目录;`--space-id` 表示数字 DingDrive 存储空间;`--workspace` 表示知识库工作区;`drive recycle restore --id` 则只接受回收站列表返回的回收项 ID。
这些值都可能表现为字符串,名称也都可能被模型概括成 `id`、`file-id`、`folder-id`、`workspace-id` 或 `space-id`。但字符串形态相同不等于值域相同。例如:
- `drive info --space-id` 不能用知识库 `workspace` 替代;
- `drive permission add --workspace` 不能用数字 `space-id` 替代;
- `drive recycle restore --id` 不能接收普通 `node-id`;
- `drive list` 和 `drive upload` 同时有 `--space-id` 与 `--workspace`,因此宽泛 `--space` 不能安全选出唯一目标。
- `dentryId` 是数字型旧标识,`dentryUuid/fileId` 才能作为公开 `--node` 的值;两者不能因为都带有 `dentry` 前缀而互换。
候选通过精确 command scope 绑定同一实体别名,并对跨值域名称进行 block 或 ambiguous:`dentry-uuid` 可归一到 `--node`,`dentry-id` 在 28 个相关命令中统一 block。它不会查询 ID、不会把 URL 解析为 node,也不会在 storage space 与 knowledge-base workspace 之间转换。
### 2.2 源对象、目标目录与创建位置角色容易互换
`drive copy`、`drive move`、`drive shortcut` 以及对应 Shortcut 同时接受源 `--node` 与目标 `--folder`/`--workspace`;`drive upload` 还同时存在本地 `--file`、覆盖目标 `--node`、目标目录和空间;`drive commit`/`mkdir` 则表达创建位置。
这类命令不能使用全局 `file-id → node`、`folder-id → folder` 就宣称治理完成,因为来源参数还必须保留 source/destination 角色。候选只在精确命令中接受 `source-node-id`、`target-folder-id`、`destination-workspace-id` 等角色明确的名称,并对 `target-id`、`destination-id` 等仍有多个合理目标的名称提示歧义。
### 2.3 检索、过滤、排序和分页参数命名分散
Drive 的搜索命令使用 `--query`,分页主要使用 `--limit`/`--cursor`,但筛选还包含创建时间、修改时间、文件类型、资源类型、操作类型、组织范围、目标范围和排序字段/方向。常见幻觉包括 `--keyword`、`--page-size`、`--page-token`、`--created-after`、`--modified-before` 和宽泛 `--types`。
候选扩展 `search_query`、`pagination_size`、`page_cursor`,新增四个时间端点 concept 和一个排序方向 concept;排序字段只在 `drive list`、`drive star list` 中用 scoped alias 归一。它只改参数名并原样传值,不做游标换算、时间格式补全、枚举翻译或多条件合并。
### 2.4 权限用户、角色、新所有者与发布权限角色不同
`drive permission add/apply/remove/update/list/transfer-owner` 使用 `users`、`role`、`filter-role`、`new-owner`、`reserve-role` 等参数;`drive publish set --permission` 又是公开发布权限,而不是协作者角色。
候选把人员列表、新所有者单值、授权角色、筛选角色和发布权限拆开:同一角色同一卡数时可归一;`new-owner-id`、`new-owner-user-id` 仅在 transfer-owner 中映射到 `new-owner`,含义不明确的 `owner-user-id` 改为 ambiguous;`user-id` 与 `users`、`role` 与 `filter-role`、协作者 role 与 publish permission 之间则拦截或提示歧义。它不把用户名解析成 userId,也不把单值包装成列表。
### 2.5 文件传输路径、名称、大小、版本和分片单位易混淆
上传和下载链路同时包含 `--file`、`--file-name`、`--output`、`--file-size`、`--part-size`、`--version`、`--upload-id`、`--parallel`、`--mime-type`。这些字段处在同一工作流中,但分别代表本地输入路径、远端显示名、本地输出路径、总字节数、分片单位、历史版本、上传会话和并发度。
候选为值可原样传递的名称增加 scoped alias,例如 `save-path → output`、`upload-session-id → upload-id`;`size-bytes → file-size` 由单位明确的 concept 支持,`version-number → version` 复用既有 concept。同时阻断 path/name、file-size/part-size、node/version 之间的错误互换。路径解析、单位换算、MIME 推断和分片计算不属于当前别名表能力。
### 2.6 宽泛名称、类型和布尔开关依赖命令上下文
`--name` 在 mkdir 与 rename 中表示目标显示名称;`file-types`、`content-types`、`resource-types`、`creator-type`、`operate-type` 分别属于不同枚举或列表;`no-resume`、`convert`、`latest`、`thumbnail`、`versions` 是不同布尔行为。
候选只在明确命令中把 `folder-name`、`display-name` 或 `file-name` 归一到 `--name`,不建立产品级 display-name concept,也不建立宽泛全局 name/type 规则;布尔和枚举值保持 Cobra 原生语义,不做值格式转换。`drive list --quiet` 已纳入参数盘点,但无需新增 alias。
### 2.7 原生隐藏兼容参数与中央别名的可发现性和实现一致性
`internal/helpers/cross_product_aliases.go` 已为 Drive 注册多组隐藏 flag,例如 `node-id`、`parent-folder-id`、`workspace-id`、`keyword`、`page-size`、`page-token`、`user` 和 `file-path`。这些参数不出现在公开 Help/Schema 中,但可能被真实 Cobra 接受。
审核候选时已移除所有与真实隐藏 flag 重复的 alias 来源,避免双重治理。最终 payload 验证还发现两项实现边界:
- `drive permission list --page-size 20` 可以解析并进入 dry-run,但最终 `maxResults` 不存在,说明实现没有读取该隐藏 flag;
- `drive upload --file-path README.md` 可以被 Cobra 接受,但实现仍报 `flag --file is required`,说明上传逻辑只读取 canonical `--file`。
这两项必须修改命令实现或移除无效隐藏 flag;`param_concepts.json` 不能接管一个已经存在的真实 Cobra flag。
此外,Skill 当前只显式覆盖 25/41 个可见工具,16 个工具没有命令级说明,`content-types`、`latest`、`new-owner`、`notify-mode`、`pattern`、`quiet`、`reason`、`recursive`、`reserve-role`、`resource-types`、`sort`、`version`、`versions` 共 13 个真实 flag 未被提到。因此 Skill 只能作为已写内容的证据,不能作为 Drive 参数面的完整清单。
## 3. 当前别名表可以实施的方案
候选草稿位于同目录 `param_concepts.json`,是冻结正式表的完整副本加 Drive 改动,而不是增量片段。
第一轮建议落地以下治理:
1. 扩展既有 `search_query`、`pagination_size`、`page_cursor`、`folder_id`、`doc_version_number` 和 `space_id` 的 Drive 精确命令范围;
2. 只新增 8 个跨命令稳定的 Drive concept:回收项 ID、创建时间起止、修改时间起止、字节数、排序方向和直接授权角色;
3. 文件名、显示名、下载输出路径、排序字段、上传会话 ID 和新所有者只配置命令级 scoped alias,不提升为产品级 concept;
4. 为 copy/move/permission/upload/download/search 等 38 个 Drive 命令配置精确 override,其中 36 个为新增;
5. 对 `dentry-id/dentry-uuid`、space/workspace、node/folder、source/destination、single/list、permission role/filter role、file-size/part-size 等冲突配置 block 或 ambiguous;
6. 保留正确的原生隐藏 compatibility flag,不在中央表重复声明;
7. 将冻结表中 `drive info --workspace → --space-id` 的旧 fixture 改为 `did-you-mean:blocked`,明确知识库工作区与数字存储空间不是同一值域。
候选生成后的 Drive 影响面为 41 个命令、376 条 alias、206 条 block、39 条 ambiguous。相较收敛前,alias 减少 30 条;`dentry-id → node` 的 28 条错误 alias 全部消失,并转为 28 条保护规则;`owner-user-id` 增加 1 条 ambiguous。数量较大主要来自 identifier/value-domain 交叉保护,仍应在正式合入时由 Drive 业务 owner 复核概念名称和枚举口径。
## 4. 当前能力支持不了或不应该做的事项
- 知识库 workspace、数字 storage space、node、folder、recycle item 之间的查询和转换;
- 根据宽泛 `--space`、`--id`、`--target-id` 自动选择多个合理目标;
- 把单个用户转换为用户列表,或把用户名解析为 userId;
- 把 page/offset/cursor 等不同分页模型互相换算;
- 修改时间格式、补全时区、推导缺失的范围端点;
- JSON、枚举、MIME、布尔值和单位转换;
- 自动读取文件、推断输出目录或计算分片参数;
- 修复已经存在但实现未读取的 `--page-size`、`--file-path` 原生隐藏 flag。
上述场景应继续使用 leaf Help/Schema 的 canonical 参数。存在多个目标时,候选会在 dispatch 前停止,不为了扩大覆盖率强行改名。
## 5. 候选草稿审核结论
候选相对冻结正式文件的结构化审核结论如下:
- 新增 8 个 concept,全部只包含 `drive ...` 命令;
- 修改 6 个既有 concept,仅增加或移除 Drive 命令范围,成员、排除项、含义和风险没有变化;
- 新增 36 个 override,全部是 Drive 精确路径;候选共有 38 个 Drive override;
- `dentry-uuid → node` 保留,`dentry-id → node` 为 0,并在 28 个相关命令中 block;
- transfer-owner 只接受语义明确的 `new-owner-id`、`new-owner-user-id → new-owner`,`owner-user-id` 为 ambiguous;
- 非 Drive override 和保护规则变化为 0;
- validation fixture 只修改 `drive info/workspace` 一条,并由错误自动映射改为安全拦截;
- 与该 active fixture 配套的 `drive info` complete-command 模板已在隔离副本移除;候选 JSON 单独替换而不做这项测试维护会触发 stale-template 门禁;
- 所有命令路径都来自同提交官方命令树;
- 与 `cross_product_aliases.go` 中真实隐藏 flag 重复的来源已移除;
- 自动 alias 都满足同实体、同角色、同值域、同单位、同 cardinality 且值可原样传递;原先不满足该条件的 `dentry-id` 已改为保护规则;
- 不能确认的映射均转为 block、ambiguous 或“当前能力不支持”。
因此收敛后的候选在语义和作用域上更合理,可进入产品评审;它没有直接修改正式工作区别名表。Skill 缺失的命令和参数不影响本轮以 Help/Schema 为主的盘点,但说明后续不能仅凭 Skill 判断覆盖完整性。
## 6. 验证结果
收敛后的候选在隔离副本中临时替换正式输入并重新生成、构建和测试,当前已验证:
- JSON 解析、生成器读取和二次生成确定性;
- 4 组代表性 alias/canonical 最终 dry-run payload 等价:commit 文件元数据、download 输出路径、list 排序、transfer-owner 新所有者;
- 2 组关键保护在 dispatch 前停止:`dentry-id`、`owner-user-id`;
- 生成结果为 41 个 Drive 命令、376 alias、206 block、39 ambiguous;
- `internal/cli`、`internal/pipeline` 包回归通过;
- 2 个 accepted-but-ignored 原生边界仍可复现:`permission list --page-size` 与 `upload --file-path`。
完整 `internal/app` 与政策门禁不能只替换候选 JSON 直接通过,因为旧的 `drive info` complete-command 模板会成为 stale template;正式落地时必须先同步删除该模板,再执行全量 app、generated drift 与 Schema policy。初版候选曾完成更大范围的 21 组 payload/14 组 guard 验证,但其规则集合已经被本次收敛替代,因此本报告不再把该数字作为当前候选的通过结论。
写命令行为验证全部使用 `--dry-run`,未发起真实业务写调用。当前工作区正式 `internal/cli/param_concepts.json` 和 `internal/cli/param_aliases_generated.go` 在验证前后均无差异。
## 7. 第一轮改造建议
1. 先合入值域明确的 identifier、search/pagination、permission role 和命令级 transfer metadata 规则,以及对应 fixture;同时删除失去 active fixture 的 `drive info` complete-command 模板;
2. 将 space-id/workspace、source/destination 和 single/list 保护作为 P0 门禁一起落地,避免 alias 只增收益而缺少风险控制;
3. 将 `dentryId` 与 `dentryUuid/fileId` 的差异作为标识符硬边界,保留 28 个命令的 `dentry-id` block 回归;
4. 单独修复 `permission list --page-size` 与 `upload --file-path` 的实现读取问题,并为它们补最终 payload 测试;
5. 正式替换前由 Drive owner 复核 `role`、`permission`、`target` 等枚举和值域说明;
6. 补齐 Skill 未覆盖的 16 个工具和 13 个真实 flag,但不要让 Skill 反向覆盖 Help/Schema;
7. 保留候选的完整行为测试矩阵,避免后续命令新增时中央规则静默扩散。
## 8. 可复用分析流程
后续产品继续使用同一流程:冻结提交并构建官方二进制 → 盘点 runtime Schema、真实 Help、Skill 和内置 Shortcut → 按业务实体、值域、角色、cardinality、单位归并问题 → 基于冻结正式表生成完整候选 → 审核真实 flag 冲突和原生 compatibility → 在隔离副本执行生成、PreParse、payload、保护、包回归和政策门禁 → 只交付产品 Markdown、五页中文 Excel 和候选草稿,不直接改正式别名表。
@@ -0,0 +1,216 @@
# DWS Drive 新增命令参数幻觉增量分析
## 1. 结论摘要
本轮最初以引入新增 Drive Shortcut 的 `main@38e387bcd6fb5806f555865c81764feba43dc6f1`(2026-08-12,PR #959)冻结产品事实;复查时已在最新 `main@e7837cdc6b5e43f74ad5483eea328a6f2d6c5995` 重新生成和验证。两者之间的更新只涉及 Skill 安装升级与 release 流程,没有修改 Drive、Schema 参数或参数归一化代码。分析按照 `specs/product-cli-param-hallucination-analysis-spec.md`,对账真实 Cobra Help、运行时组装 Schema、Drive Skill、Shortcut 实现和当前正式 `internal/cli/param_concepts.json`。未使用历史 badcase、`dws-eval`、历史 Excel、固定 Catalog、用户自定义 Shortcut 或插件。
本次合入后,Drive 在运行时 Schema 中由 **41 个工具增至 63 个**,新增 **22 条 `drive +...` 路径**。这 22 个命令共有 **55 次业务参数、21 个不同 canonical flag 名**;逐命令核对 Help 与同提交 Schema,公开参数名、类型和必填关系差异为 **0**。因此问题不是 Schema 快照过期,而是新增命令没有同步进入按精确路径生效的中央参数兜底表。
本轮正式落地前,生成表对这 22 条新增路径没有独立规则。代表性问题如下:
- `drive +list --folder-id`、`--page-size`、`--next-token` 均报 unknown flag;
- `drive +download --dentry-uuid` 与 `--destination-path` 均不能进入 canonical 参数;
- `drive +recycle-restore --recycle-item-id` 不能归一到真实 `--id`;
- `drive +version-get --version-number` 不能归一到 `--version`;
- 更危险的是,`drive +delete --name`、`drive +version-revert --name` 会被通用拼写纠错当成 `--node`,分别到达确认门和后续执行链路,而不是以错误参数停止。
新增参数问题可聚合为 **7 类**:Drive 节点 ID 命名和值域边界、位置 ID 与源/目标角色、分页与排序、传输与版本工作流、宽泛名称的模糊纠错、类型/聚合开关,以及 Schema/Skill/生成器可见性漂移。
本轮实现基于原正式表增量修改,没有新增产品级 concept,而是:
- 扩展 **9 个既有 concept** 的 Drive 精确命令范围;
- 新增 **20 个精确 Drive command override**;
- 新增 **72 条审核 fixture**,其中 64 条 alias、8 条 guard;
- 隔离生成后覆盖 21 个公开新增命令,共产生 **220 条 alias、170 条 block、10 条 ambiguous**。
数量较大并不表示有 400 个独立问题:分页、空间 ID 和节点 ID 的 concept 成员及 excludes 会按命令展开。第二轮审核以既有等价命令为契约证据,补齐 `node-id` 和已验证的 ID/URL 输入,同时删除与旧命令冲突的过度 block;保留的 170 条 block 主要用于阻止数字 `dentryId → node`、`name → node`、普通文件与 Doc URL 混用、space/workspace 和源/目标角色混用。所有规则都按精确命令路径收敛,不扩散到其他产品。
当前结论是“**第二轮语义校准、正式表替换和完整测试落地均已完成**”。21 个新增公开命令均已补 complete-command 模板,64 条 alias fixture 均验证 alias 与 canonical 到达完全相同的最终 transport payload;8 条 block/ambiguous fixture 继续在 dispatch 前停止。生成器、`internal/cli`、`internal/pipeline`、Drive、完整 `internal/app`、generated drift、Schema policy 以及除未跟踪 `outputs/` 分析脚本外的全部正式 Go package 均通过。
另有一个不能由别名表解决的契约问题:`drive +publish-set` 出现在运行时 Schema 且显示 `availability=available`,但 `semantic_catalog_drive.json` 将它定义为 `public=false、availability=unavailable`,Drive Skill 也不把它列为公开命令。参数 alias 生成器按审核后的可运行叶子拒绝该路径,所以正式规则没有强行加入它。需要先统一 Shortcut 可见性、Schema 发布和 Skill,再决定是否治理它的 `--node/--permission`。
## 2. 新增命令范围
新增 22 条 Schema 路径如下:
```text
drive +cover drive +create-folder drive +create-shortcut
drive +delete drive +download drive +inspect
drive +list drive +publish-get drive +publish-set
drive +publish-unset drive +recycle-list drive +recycle-restore
drive +rename drive +star-add drive +star-list
drive +star-remove drive +stats drive +upload
drive +version-download drive +version-get drive +version-history
drive +version-revert
```
其中 21 条属于语义目录和 Drive Skill 认可的公开 Shortcut;`drive +publish-set` 是上述可见性漂移的例外。完整参数明细见配套工作簿“参数问题明细”。
## 3. 参数问题
### 3.1 Drive 节点 ID 命名和值域边界
17 个新增命令使用真实 `--node`,其中 16 个属于公开 Shortcut,`+publish-set` 是当前可见性漂移的例外。其值会原样进入底层 `fileId` 或 `nodeId`。第二轮复查进一步确认,不能把这 16 个命令都按同一套“仅 ID、拒绝 URL”的规则处理:应先保证通用节点 ID 名称一致,再按接口和既有等价命令收敛 URL/文档节点边界。
因此:
- 16 个公开 node 命令全部支持 `dentry-uuid/file-id/node-id → node`;
- `dentry-id` 必须 block;
- `+cover/+create-shortcut/+publish-get/+publish-unset/+rename/+star-add/+star-remove/+stats` 与已有 Drive 命令调用相同接口和字段,恢复已有命令已经验证的 `doc-id/document-id/url/document-url/id → node` 范围;
- `+inspect` 明确接收文件、文件夹或文档节点 ID,因此接受 `doc-id/document-id/folder/folder-id → node`,但不扩展 URL;
- `+delete/+rename/+inspect` 操作的单一目标可以是文件夹,`folder/folder-id → node` 没有源/目标角色歧义;
- `+download/+version-*` 仍是普通文件工作流,继续 block Doc URL、folder 和本地输入角色;
- `+upload file-id/node-id` 明确表示覆盖目标 `node`,而无 `-id` 的 `file/file-path` 仍表示本地输入;
- 其余宽泛 `id` 是否接受,以同接口既有命令证据为准;没有证据时保持 block/ambiguous。
这里仍不直接复用 `doc_node_id` concept:不同 Drive Shortcut 对 URL、普通文件、文件夹和文档节点的接受范围并不相同。新增一个包含相同 `node/file-id/dentry-uuid` 成员的 `drive_node_id` 也不可行,因为生成器禁止两个 concept 共享成员。正式实现采用精确 command override,并以同接口旧命令和当前 Shortcut 的真实参数组装共同确定每条命令的边界。
### 3.2 位置标识符和源/目标角色容易互换
`+create-folder`、`+create-shortcut`、`+list` 和 `+upload` 同时涉及父目录、源节点、覆盖节点、数字存储空间或知识库工作区:
- `--folder` 接收父/目标文件夹的 dentryUuid;
- `--space-id` 接收数字 DingDrive 存储空间 ID;
- `--workspace` 只在 `+create-shortcut` 表示目标知识库;
- `+upload --node` 是覆盖目标,不是本地输入文件;
- `+recycle-restore --id` 是 recycleItemId,不是节点 ID。
正式规则只接受角色明确的 `source-file-id`、`target-folder-id`、`target-workspace-id`、`overwrite-node-id`;`target-id`、`destination-id`、`space` 等有多个合理目标的名称保留 ambiguous。`+upload --workspace-id` 被 block,因为新 Shortcut 并没有旧 `drive upload --workspace` 的知识库路由能力。
### 3.3 分页和排序参数没有继承旧命令规则
`+list`、`+recycle-list`、`+star-list`、`+version-history` 使用 `--limit/--cursor`,但旧规则只覆盖 `drive list/recycle list/star list` 等精确路径。`+list` 还使用 `--order-by/--order`。
正式规则扩展既有 `pagination_size`、`page_cursor` 和 `drive_sort_direction`,支持值不变的 `page-size/max-results → limit`、`page-token/next-token → cursor`、`sort-direction → order`;`sort-by/order-field → order-by` 只在 `+list` 用 scoped alias。`page`、`offset` 不会转换成 cursor,防止分页模型和值发生变化。
### 3.4 传输、版本、名称和输出路径角色相近
`+download/+version-download` 的 `--output` 是本地输出路径;`+upload --file` 是本地输入路径,`--file-name` 是远端显示名称,`--node` 是覆盖目标;`+version-get/+version-download/+version-revert` 的 `--version` 是正整数历史版本号;`+rename --name` 是新显示名称。
正式规则仅做可原样传递的改名:
- `destination-path/save-path → output`;
- `source-file/local-file/file-path → file`;
- `display-name/upload-name/name → file-name`,仅限 `+upload`;
- `new-name/display-name/file-name → name`,仅限 `+rename`;
- `version-number/version-no → version`;
- `content-type → mime-type`。
它不会读取文件、转换绝对路径、推断 MIME、换算版本或把输出路径当输入路径。
### 3.5 `name → node` 的通用模糊纠错风险
本轮落地前的正式表没有保护新增的“只有目标 `--node`、没有真实 `--name`”命令。`name` 与 `node` 编辑距离很近,实测:
```text
dws drive +delete --name fixture
→ 被当成 --node fixture
→ 到达高风险确认门
dws drive +create-shortcut --name fixture
→ 被当成 --node fixture
→ 继续进入 API/鉴权链路
```
这不是显式 alias,而是中央 semantic alias 没命中后,通用 ParamName 模糊纠错接管。正式规则在没有真实 `--name` 的新增 node 命令上精确 block `name`,使它在 dispatch 前返回 `blocked_flag`。`+rename` 保留真实 `--name`,`+upload` 则将 `name` 明确限定为远端 `--file-name`,不会一刀切拦截。
### 3.6 类型过滤和聚合开关不能凭相似名称猜测
`+star-list --content-types` 的值是 API contentTypes 列表,宽泛 `--type/--types` 也可能表达文件扩展名、节点类型或资源类型,正式规则标记 ambiguous,不自动转换。
`+inspect` 只支持 `--include-stats/--include-publish/--include-cover`。正式规则允许语义明确的 `include-statistics`、`include-public-status`、`include-thumbnail`,但 block `--include` 以及 Doc `+inspect` 的 `include-history/include-permissions/include-content`。当前链路只能改一个参数名,不能根据 `--include history,stats` 拆成多个布尔 flag。
### 3.7 Schema、Skill 和 alias 生成器的可见性漂移
`drive +publish-set` 的三个事实互相冲突:
- 运行时 Schema:路径存在,`availability=available`,发布 `--node/--permission`;
- 语义目录:`public=false`、`availability=unavailable`,原因是服务端对已验证样本返回不支持;
- Drive Skill:公开 Shortcut 清单不包含它。
在隔离候选中添加该 command override 时,生成器报:
```text
command_override "drive +publish-set" does not match any runnable Cobra leaf
```
所以它当前不能用 JSON 稳定治理。应先决定它是公开可用命令还是隐藏诊断命令,并统一声明、Schema 和 Skill;之后才能加入参数别名与最终 payload 测试。其余 21 个公开命令不受影响,均已正式落地。
同时,既有 `drive +find-file` 仍把 `dentryId/dentryUuid/fileId/nodeId/id` 多种返回候选统一投影到字段名 `dentryId`。这是输出标识符命名问题,不能由入参 alias 解决,应单独修改结果投影和 Schema result 契约。本轮未把它伪装成新增命令的 alias 问题。
## 4. 已实施的正式别名表方案
审核草稿位于同目录 `param_concepts.json`;其内容现已同步到正式 `internal/cli/param_concepts.json`,并通过生成器生成 `param_aliases_generated.go`。
本轮已经落地:
1. 为 16 个公开 node 命令完整配置 `dentry-uuid/file-id/node-id → node`,并按同接口旧命令决定是否接受文档 ID/URL;
2. 扩展 9 个既有 concept:`pagination_size`、`page_cursor`、`folder_id`、`drive_storage_space_id`、`doc_version_number`、`drive_recycle_item_id`、`drive_sort_direction`、`workspace_id`、`local_output_path`;
3. 为 `+create-folder/+create-shortcut/+list/+upload` 配置角色明确的命令级 alias,并保护 space/workspace、source/destination、folder/node;
4. 为 `+inspect`、`+rename`、`+upload` 配置只在该命令成立的 section/name/path alias;
5. 对 `name → node`、`dentryId → node`、普通文件工作流中的 Doc URL、普通 node → recycleItemId 配置 dispatch 前保护;
6. `+star-list --type/--types` 保持 ambiguous;
7. 暂不为 `+publish-set` 增加规则,先修复可见性契约。
## 5. 当前能力支持不了或不应该做的事项
- 把数字 `dentryId`、知识库 workspace、数字 storage space、Drive node 和 recycleItemId 相互查询或转换;允许 URL 的命令只做已验证的名称归一,不做 URL→ID 转换;
- 根据 `--id/--space/--target-id/--destination-id` 自动选择多个合理目标;
- 把 page/offset 换算成 cursor,或生成下一页 token;
- 把 `--include` 的值拆为多个布尔 flag;
- 把宽泛 `--types` 的值翻译成 contentTypes 枚举;
- 读取本地文件、转换路径、推断 MIME 或修改参数值;
- 修复 `+find-file` 的输出字段混名;
- 在 `+publish-set` 可见性契约统一前,为生成器不可接受的隐藏路径强行添加 override。
上述事项不阻塞其余 21 个公开新增命令的第一轮治理,但必须保持 block、ambiguous 或明确待修,不应为了覆盖率配置猜测性 alias。
## 6. 正式落地审核结论
相对本轮修改前的正式 `internal/cli/param_concepts.json`:
- 新增 concept:0;
- 修改既有 concept:9,仅增加新增 Drive 命令范围;其中 `doc_version_number.denotes` 文案扩为“Doc 或 Drive 普通文件历史版本”,members/excludes 不变;
- 新增 command override:20,全部为本轮 Drive `+` 命令;
- 新增 validation fixture:72,其中 64 条 alias、8 条 block/ambiguous guard,全部可追溯到本文问题;
- 非 Drive command override、concept 命令范围和 fixture 改动:0;
- `drive +publish-set` 经审核后从草稿移除,原因是生成器与 Schema 可见性冲突;
- 曾尝试新增 `drive_node_id`,因与 `doc_node_id` 共享成员会被生成器拒绝,审核后移除,改为精确 command override;
- 自动 alias 均满足同一实体、角色、值域、单位和 cardinality,值原样传递;新增的 URL 别名只覆盖同接口旧命令已接受 URL 的精确路径,不声称进行 URL 解析或值转换;不满足条件的名称均为 block/ambiguous/暂不支持。
正式生成结果覆盖 21 个公开新增命令,展开为 220 alias、170 block、10 ambiguous。规则规模主要由通用 concept 成员、excludes 和精确 command override 展开,未扩大到插件、用户 Shortcut 或其他产品。
## 7. 正式验证结果
正式表替换并重新生成后,验证结果如下:
- `jq` 解析与 JSON Schema/生成器读取:通过;
- `go generate ./internal/cli`:通过,两次生成结果确定;
- `go test ./internal/cli ./internal/pipeline ./internal/generator/cmd_param_aliases`:通过;
- 全量 reviewed guard 到真实 runtime contract:通过;
- fixture 经过最终嵌入交付路径:通过;
- `check-generated-drift.sh`:通过;
- `check-schema-catalog.sh`:通过;
- 21 个新增公开命令 complete-command E2E 模板:全部存在并满足真实 required/constraint;
- 64 条 Drive alias/canonical 最终 transport payload 等价测试:全部通过;
- 8 条 Drive block/ambiguous guard:全部通过并在 dispatch 前停止;
- 完整 `internal/app`:通过;
- 除未跟踪 `outputs/` 分析脚本外的全部正式 Go package:通过;
- 代表性 alias:`+list`、`+download`、`+recycle-restore`、`+version-get` 以及第二轮补充的 `+cover --node-id/--url`、`+create-shortcut --document-id`、`+delete/+inspect/+rename --folder-id`、`+star-add --url`、`+stats --document-url`、`+upload --file-id` 均不再报 unknown flag,进入 canonical 对应的 mock、确认或本地校验链路;
- 代表性保护:`+create-shortcut --name`、`+delete --name` 返回 `blocked_flag`,`+star-list --types` 返回 `ambiguous_flag`,`+upload --workspace-id` 返回 `blocked_flag`,均在 dispatch 前停止;
- 未发起真实业务写操作:最终 payload 测试统一注入 capture caller/runner;下载使用不受信任 URL 的稳定校验边界,上传停在受控凭证响应校验边界,alias 与 canonical 的调用序列和错误均一致。
本轮没有剩余的参数别名落地门禁。仍待单独处理的是 `+publish-set` 可见性契约和 `+find-file` 输出字段命名,它们不属于 `param_concepts.json` 能解决的入参别名问题。
## 8. 后续事项
1. 先修复 `drive +publish-set` 的 Hidden/availability/Schema/Skill 一致性,明确它是否进入公开产品面;
2. 后续修改这些 Drive 参数规则时,保持 21 个 complete-command 模板和 64 条 payload 等价测试同步更新;
3. 把 `name → node` 和 `dentryId → node` 作为 P0 保护,不只增加收益 alias;
4. 对 `+create-shortcut`、`+upload`、`+recycle-restore`、`+version-revert` 等写命令使用注入 Runner 或确认门验证,禁止真实写调用;
5. 单独修复 `+find-file` 输出把多种 ID 命名为 `dentryId` 的契约问题,不放入本次入参 alias 改造;
6. 后续若扩展 `content-types`、公开权限 enum 或普通文件版本值域,应由 Drive owner 重新复核。
## 9. 可复用流程
后续继续使用:冻结最新 main 并从同提交构建 → 比较新增前后官方 Schema 路径 → 对账每条新增命令的 Help、完整 Schema、Skill 和实现 → 按实体/值域/角色/cardinality 聚合问题 → 从正式表生成完整候选 → 独立生成审计并移除冲突 concept/隐藏路径 → 验证 alias/canonical 最终 payload、block/ambiguous 和非目标回归 → 测试模板全绿后再替换正式表。
@@ -0,0 +1,169 @@
# DWS Drive 新增 Shortcut 参数幻觉复查报告
## 1. 结论摘要
本轮按照 `specs/product-cli-param-hallucination-analysis-spec.md`,在 `fix/param-hallucination@0dc6735da2cbdd511272fcbe2282d0e28c54baeb` 重新构建 `dws`,复查 PR #959 新增或转为公开的 Drive Shortcut。当前远端 `main@76d54d6df6a5fffef91a5af68492f4620961616e` 相比分析提交只增加 `CHANGELOG.md`,Drive 命令、Schema 和参数别名代码没有差异,因此本轮参数结论同样适用于该最新 main。
本轮范围包含 **21 条公开新增 Shortcut**,共有 **53 次业务参数出现、20 个不同 canonical flag**。另有 `drive +publish-set` 可通过真实 Help 和运行时 Schema 查询,但不进入 `dws shortcut list --service drive` 的 28 条公开清单,也没有进入 Drive Skill 的公开路由;将它计入技术命令面后,范围为 **22 条命令、55 次参数出现、21 个不同 flag**。
核心结论:
- 21 条公开新增 Shortcut 的 Help、运行时 Schema 和 Shortcut 声明参数一致,参数名、类型和必填关系差异为 **0**;
- 当前正式 `internal/cli/param_concepts.json` 已经覆盖全部 21 条公开命令:有效结果为 **220 条 alias、170 条 block、10 条 ambiguous**;
- 当前有 **72 条审核 fixture** 覆盖这 21 条命令,其中 64 条验证 alias,8 条验证 block/ambiguous;每条公开命令至少有一条 fixture;
- 现有完整候选 `docs/parameter-hallucination/drive/param_concepts.json` 已刷新为当前正式表的完整副本,结构化 diff 为 **0**。本轮没有发现需要再次扩大 alias 的安全映射;
- 唯一新增风险是 `drive +publish-set` 的可见性契约漂移。它仍不能进入参数 alias 生成器的可治理叶子集合,向候选表增加 override 会失败;同时当前 `--name` 会被通用模糊纠错成 `--node` 并进入高风险 Shortcut 执行链路。该问题需要先统一 Cobra/Schema、Shortcut Catalog、Skill 和生成器边界,不能由现有 JSON 单独修复。
本轮未使用历史 badcase、`dws-eval`、`merged_scan.json`、历史固定 Catalog、用户自定义 Shortcut 或插件。
## 2. 分析范围
21 条公开新增 Shortcut:
```text
drive +list drive +inspect drive +download
drive +upload drive +create-folder drive +create-shortcut
drive +rename drive +delete drive +stats
drive +cover drive +recycle-list drive +recycle-restore
drive +star-list drive +star-add drive +star-remove
drive +publish-get drive +publish-unset drive +version-history
drive +version-get drive +version-download drive +version-revert
```
单独审核但不计入公开范围:
```text
drive +publish-set
```
## 3. 参数问题与现有兜底结论
### 3.1 节点 ID 命名、URL 和值域边界
16 条公开命令使用 `--node`,但 Agent 容易生成 `--file-id`、`--node-id`、`--dentry-uuid`、`--document-id`、`--url`、`--folder-id` 或数字 `--dentry-id`。
这些名称不能一刀切:
- 所有 16 条命令都可安全接受值不变的 `dentry-uuid/file-id/node-id → node`;
- `+cover/+create-shortcut/+publish-get/+publish-unset/+rename/+star-add/+star-remove/+stats` 按同接口既有命令证据接受文档 ID/URL;
- `+inspect` 接受文件、文件夹或文档节点 ID,但不声明 URL 输入;
- `+delete/+rename/+inspect` 的单一目标可以是文件夹,因此 `folder/folder-id → node` 角色明确;
- `+download/+version-*` 是普通文件工作流,文档 URL、文件夹和本地路径必须拦截;
- 数字 `dentryId` 不能只改名变成 dentryUuid/fileId,必须 block。
当前正式表已通过精确 command override 实现上述边界,并对 14 条没有真实名称参数的 node 命令保护 `name → node` 模糊纠错。`+rename` 保留真实 `--name`,`+upload` 则把 `name` 精确归入远端 `--file-name`,没有误拦截。
### 3.2 存储空间、知识库、父目录和源/目标角色
`+list/+create-folder/+create-shortcut/+upload/+recycle-restore` 同时出现 `space-id`、`workspace`、`folder`、`node` 或宽泛 `id`:
- `--space-id` 是数字 DingDrive 存储空间;
- `--workspace` 是知识库/文档空间;
- `--folder` 是父目录或目标目录;
- `+upload --node` 是覆盖目标,不是本地输入文件;
- `+recycle-restore --id` 是 recycleItemId,不是普通节点 ID。
现有表使用 concept、bind、scoped alias、block 和 ambiguous 分离这些角色。`target-folder-id`、`target-workspace-id`、`overwrite-node-id` 等角色明确的名称可归一;`target-id`、`destination-id`、`space` 等存在多个目标的名称继续提示歧义。
### 3.3 分页与排序命名不一致
`+list/+recycle-list/+star-list/+version-history` 使用 `limit/cursor`,`+list` 还同时使用 `order-by/order`。模型容易沿用 `page-size/max-results/page-token/next-token/sort-by/sort-direction`。
现有 `pagination_size`、`page_cursor`、`drive_sort_direction` 和 `+list` 精确 alias 已覆盖值不变的名称;`page/offset` 仍被拦截,因为当前链路不能把页码或偏移换算成 cursor。
### 3.4 本地文件、输出路径、远端名称和版本号角色相近
`+download/+upload/+rename/+version-get/+version-download/+version-revert` 同时出现:
- 本地输入 `file`;
- 本地输出 `output`;
- 远端显示名称 `file-name/name`;
- MIME `mime-type`;
- 覆盖目标 `node`;
- 正整数历史版本 `version`。
当前表只做值可原样传递的改名,例如 `destination-path/save-path → output`、`source-file/file-path → file`、`content-type → mime-type`、`version-number/version-no → version`。它不会读取文件、转换绝对路径、推断 MIME、换算版本或把输入路径和输出路径互换。
### 3.5 聚合开关和类型过滤不能使用宽泛名称
`+inspect` 只支持 `include-stats/include-publish/include-cover`;现有规则允许 `include-statistics/include-public-status/include-thumbnail`,但拦截需要值拆分的泛化 `--include` 以及 Doc `+inspect` 的其他 section 名称。
`+star-list --content-types` 的值域可能与文件扩展名、节点类型或资源类型混淆,因此 `--type/--types` 保持 ambiguous。`+list --thumbnail` 在稳定命令和 Shortcut 中命名一致,无需新增 alias。
### 3.6 `drive +publish-set` 可见性和治理边界不一致
当前事实同时存在:
1. `dws drive +publish-set --help` 可找到真实叶子;
2. `dws schema --cli-path "drive +publish-set"` 发布 `availability=available`、`--node/--permission` 和高风险确认;
3. `dws shortcut list --service drive` 不公开它,Drive Skill 也明确不推荐;
4. 参数 alias 生成器拒绝该路径:`command_override "drive +publish-set" does not match any runnable Cobra leaf`;
5. 当前 `--name fixture` 不会报 unknown flag,而是被模糊纠错成 `--node fixture` 并进入 Shortcut 执行链路;仍有 `--yes`/确认和后端校验,但参数名保护缺失。
因此本轮没有把 `+publish-set` override 强行留在候选表。安全顺序应是:先明确它是否公开可用;若不公开,应同步从可执行/Schema 面隐藏;若公开,应先让生成器和 Catalog 边界一致,再增加 `name/dentry-id` 保护、审核后的 node/permission alias 和最终 payload/确认回归。
## 4. 当前正式别名表覆盖情况
对 21 条公开新增 Shortcut,当前正式表有效使用:
- 15 个已有 concept;
- 20 个精确 command override,`+recycle-list` 仅依靠已有 concept 即可完成;
- 220 条生成 alias;
- 170 条生成 block;
- 10 条生成 ambiguous;
- 72 条审核 fixture:64 条 alias、8 条 guard;
- 21 条 complete-command 模板和最终 transport payload/确认保护测试。
这些数字是 concept 成员、excludes 和 command override 按真实 flag 交集后的展开结果,不代表 400 个独立问题。规则均收敛到审核过的精确路径,没有扩散到其他产品、用户 Shortcut 或插件。
## 5. 当前能力支持不了或不应该做的事项
- 把数字 dentryId、dentryUuid/fileId、知识库 workspace、数字 storage space 和 recycleItemId 相互查询或转换;
- 根据 `--target-id/--destination-id/--space` 自动选择多个合理 canonical 目标;
- 把 page/offset 换算成 cursor,或生成下一页 token;
- 把一个 `--include` 值拆成多个布尔 flag;
- 翻译 `--types` 的枚举值或在不同类型值域之间转换;
- 读取本地文件、转换路径、推断 MIME 或修改参数值;
- 在 `+publish-set` 被生成器排除时,仅靠 JSON 为它增加 block/ambiguous。
这些限制不阻塞 21 条公开新增 Shortcut;`+publish-set` 需要单独修复命令可见性契约。
## 6. 第一轮改造建议
1. **公开 21 条 Shortcut 不再修改正式别名表**:现有规则覆盖完整,继续扩大 alias 反而会增加值域误判;
2. **修复 `+publish-set` 可见性契约**:由 Drive owner 决定隐藏还是公开,并统一 Cobra、Schema、semantic catalog、Skill 和 alias 生成器;
3. **保留现有安全保护**:`name → node`、`dentryId → node`、space/workspace、source/destination 和 recycleItem/node 边界不可收缩;
4. **后续变更必须同步测试**:维持 72 条 fixture、21 条完整模板、64 条 alias/canonical payload 等价和高风险确认保护。
## 7. 候选别名表审核结论
`docs/parameter-hallucination/drive/param_concepts.json` 已从当前正式 `internal/cli/param_concepts.json` 重新生成完整副本。
结构化审核结果:
- 新增 concept:0;
- 修改 concept:0;
- 新增 command override:0;
- 修改 command override:0;
- fixture 差异:0;
- 非 Drive 规则差异:0;
- 候选与正式文件字节一致。
曾在隔离 worktree 尝试给 `+publish-set` 增加“只保护、不增加 alias”的 override,但生成器拒绝该隐藏路径,因此审核后移除并转入当前不支持清单。候选表当前状态是“**已审核、无需替换正式表**”。
## 8. 验证结果
在独立临时 worktree 中把候选文件作为正式输入后:
- `jq empty`:通过;
- `go generate ./internal/cli`:通过,生成 340 条命令级 alias entry;
- 生成结果与当前提交一致:通过;
- `go test ./internal/cli ./internal/pipeline ./internal/generator/cmd_param_aliases ./internal/app -count=1`:通过;
- 21 条 Drive Shortcut payload 等价、确认保护和最终嵌入 fixture 定向测试:通过;
- `check-generated-drift.sh`:通过;
- `check-schema-catalog.sh`:通过,27 个产品、1098 个工具;
- 未发起真实业务写调用:写操作测试使用注入调用器/Runner 或确认保护。
## 9. 可复用流程
冻结当前提交并重新构建 → 以 Shortcut Catalog 确认公开范围 → 用 Help/完整 Schema/Skill/实现对账 flags → 按实体、角色、值域和 cardinality 聚合问题 → 对照正式别名表的有效生成结果 → 只为安全且值可原样传递的缺口生成候选 → 独立 worktree 验证生成、payload、保护和非目标回归 → 可见性或值转换问题转入当前不支持,不强行写入 JSON。
@@ -0,0 +1,654 @@
{
"$schema": "./param_concepts.schema.json",
"version": 1,
"morphological_rules": {
"kebab_camel_equivalence": {"desc": "--page-size == --pageSize", "enabled": true},
"separator_normalization": {"desc": "-, _, . are equivalent separators", "enabled": true},
"trailing_id_tolerance": {"desc": "--base tolerates --base-id when only one is a real flag on the command", "enabled": true, "guard": "the two must not both be real flags with different semantics"},
"pluralization": {"desc": "--id<->--ids, --user<->--users", "enabled": false, "reason": "singular/list semantics can differ; handled by concept+intersection or command override instead"}
},
"concepts": {
"search_query": {"denotes": "search keyword string", "canonical_hint": "query", "members": ["query", "keyword", "keywords", "q", "search-word"], "excludes": ["name", "subject", "text", "title"], "commands": ["aitable +base-search", "contact +dept-members", "contact +resolve-dept", "contact +search-user", "doc +create-from-template", "doc +find-doc", "doc +search", "doc +template-search", "doc template search", "drive +find-file", "drive +search", "drive +search-docs", "drive search", "mail +find-mail-user", "mail user search", "oa +search-forms", "oa approval search-forms"], "risk": "green"},
"pagination_size": {"denotes": "returned item count upper bound", "canonical_hint": "limit", "members": ["limit", "size", "page-size", "max-results", "max-result", "take", "top", "per-page"], "excludes": ["count", "page", "cursor"], "commands": ["aitable record query", "calendar event list", "chat message list", "devdoc article search", "doc +comment-list", "doc +find-doc", "doc +list", "doc +search", "doc +template-list", "doc +template-search", "doc +version-list", "doc comment list", "doc template list", "doc template search", "doc version list", "drive +list", "drive +recent", "drive +recycle-list", "drive +search", "drive +search-docs", "drive +star-list", "drive +version-history", "drive list", "drive list-spaces", "drive permission list", "drive recent", "drive recycle list", "drive search", "drive star list", "mail thread list", "oa +list-executed"], "risk": "green"},
"page_number": {"denotes": "one-based page number", "canonical_hint": "page", "members": ["page", "page-no", "current-page", "page-num"], "excludes": ["cursor", "page-index", "page-size", "page-token"], "commands": ["devdoc article search"], "risk": "green"},
"page_cursor": {"denotes": "pagination cursor/token", "canonical_hint": "cursor", "members": ["cursor", "next-cursor", "page-token", "next-token", "next-page-token"], "excludes": ["page", "offset"], "commands": ["calendar event list", "doc +comment-list", "doc +list", "doc +search", "doc +template-list", "doc +template-search", "doc +version-list", "doc comment list", "doc template list", "doc template search", "doc version list", "drive +list", "drive +recent", "drive +recycle-list", "drive +search", "drive +star-list", "drive +version-history", "drive list", "drive list-spaces", "drive recent", "drive recycle list", "drive search", "drive star list"], "risk": "green"},
"content_text": {"denotes": "text body content", "canonical_hint": "text", "members": ["text", "content", "body"], "excludes": ["title", "name"], "commands": ["doc +checkpoint-update", "doc +comment-create", "doc +comment-reply", "doc +comment-update", "doc +create", "doc +doc-append", "doc block insert", "doc block update", "doc comment create", "doc comment create-inline", "doc comment reply", "doc comment update", "doc create"], "risk": "green"},
"time_start": {"denotes": "start time point with unchanged value format and unit", "canonical_hint": "start", "members": ["start", "start-time", "start-date", "from", "from-date", "begin", "since", "time-min", "min-time"], "excludes": ["date", "time", "end"], "commands": ["calendar event list", "chat message list-all", "report list"], "risk": "yellow"},
"time_end": {"denotes": "end time point with unchanged value format and unit", "canonical_hint": "end", "members": ["end", "end-time", "end-date", "time-max", "max-time"], "excludes": ["date", "time", "start"], "commands": ["calendar event list"], "risk": "yellow"},
"base_id": {"denotes": "multi-dimensional table Base id", "canonical_hint": "base-id", "members": ["base", "base-id", "base-token"], "excludes": [], "commands": ["aitable +field-get", "aitable +list-tables", "aitable +record-query", "aitable +record-share-url", "aitable +table-get"], "risk": "green"},
"dept_id": {"denotes": "single department id", "canonical_hint": "dept", "members": ["dept", "dept-id", "department", "department-id", "parent", "parent-id"], "excludes": ["depts", "dept-ids", "department-ids", "name", "query"], "commands": ["contact +list-sub-depts", "contact dept list-children"], "risk": "yellow"},
"dept_ids": {"denotes": "department id list", "canonical_hint": "dept-ids", "members": ["depts", "dept-ids", "department-ids"], "excludes": ["dept", "dept-id", "department-id", "name", "query"], "commands": ["contact +list-dept-members"], "risk": "yellow"},
"group_id": {"denotes": "single DingTalk numeric groupId", "canonical_hint": "group-id", "members": ["group-id"], "excludes": ["group", "conversation-id", "chat", "chat-id", "open-conversation-id", "conversation-ids", "open-conversation-ids", "group-name", "name", "id"], "commands": ["chat +chat-get-by-id", "chat group get-by-group-id"], "risk": "yellow"},
"open_conversation_id": {"denotes": "single DingTalk openConversationId with unchanged value", "canonical_hint": "conversation-id", "members": ["group", "conversation-id", "chat", "chat-id", "open-conversation-id"], "excludes": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids", "group-name", "name", "id", "source", "target", "src-conversation-id", "dest-conversation-id"], "commands": ["chat +category-add-conversation", "chat +category-remove-conversation", "chat +chat-add-bot", "chat +chat-audit-join", "chat +chat-bots", "chat +chat-dismiss", "chat +chat-invite-url", "chat +chat-members-get", "chat +chat-mute", "chat +chat-mute-member", "chat +chat-quit", "chat +chat-remove-bot", "chat +chat-role-add", "chat +chat-role-list", "chat +chat-role-query-user", "chat +chat-role-remove", "chat +chat-role-remove-user", "chat +chat-role-set-user", "chat +chat-role-update", "chat +chat-set-admin", "chat +chat-set-history", "chat +chat-transfer-owner", "chat +chat-update-alias", "chat +chat-update-icon", "chat +chat-update-nick", "chat +chat-update-settings", "chat +conversation-clear-messages", "chat +conversation-clear-red-point", "chat +conversation-hide", "chat +conversation-info", "chat +conversation-mark-read", "chat +conversation-mark-unread", "chat +conversation-mute", "chat +flag-cancel", "chat +flag-create", "chat +messages-add-emoji", "chat +messages-add-text-emotion", "chat +messages-list-pin", "chat +messages-read-status", "chat +messages-recall-by-bot", "chat +messages-remove-emoji", "chat +messages-remove-text-emotion", "chat +messages-reply", "chat +messages-resource-download", "chat +messages-resource-url", "chat +messages-send-by-bot", "chat +messages-set-pin", "chat +messages-set-top", "chat +messages-unset-pin", "chat +messages-unset-top", "chat category add-conv", "chat category remove-conv", "chat chmod", "chat clear-messages", "chat clear-red-point", "chat conversation-info", "chat group audit-join-validation", "chat group bots", "chat group dismiss", "chat group invite-url", "chat group members", "chat group members add", "chat group members add-bot", "chat group members list-by-ids", "chat group members remove", "chat group members remove-bot", "chat group notice create", "chat group notice edit", "chat group notice get", "chat group notice list", "chat group quit", "chat group rename", "chat group set-admin", "chat group set-history", "chat group transfer-owner", "chat group update-alias", "chat group update-icon", "chat group update-nick", "chat group update-settings", "chat group-mute", "chat group-mute-member", "chat group-role add", "chat group-role list", "chat group-role query-user", "chat group-role remove", "chat group-role remove-user", "chat group-role set-user", "chat group-role update", "chat hide", "chat mark-read", "chat mark-unread", "chat message add-favorite", "chat message download-media", "chat message list", "chat message list-mentions", "chat message list-pin-msg", "chat message list-topic-replies", "chat message read-status", "chat message recall", "chat message recall-by-bot", "chat message remove-favorite", "chat message reply", "chat message search", "chat message send", "chat message send-by-bot", "chat message send-card", "chat message set-pin-msg", "chat message set-top-msg", "chat message unset-pin-msg", "chat message unset-top-msg", "chat mute-at-all", "chat mute-red-envelope", "chat set-top"], "risk": "yellow"},
"open_conversation_ids": {"denotes": "DingTalk openConversationId list with unchanged element values", "canonical_hint": "conversation-ids", "members": ["conversation-ids", "open-conversation-ids", "groups"], "excludes": ["group-id", "group-ids", "conversation-id", "open-conversation-id", "chat-id"], "commands": ["chat message search-advanced"], "risk": "yellow"},
"group_name": {"denotes": "group-name search keyword, not a group identifier", "canonical_hint": "group-name", "members": ["group-name"], "excludes": ["group-id", "conversation-id", "open-conversation-id", "chat-id", "id"], "commands": ["chat +group-members", "chat +send-to-group"], "risk": "yellow"},
"open_message_id": {"denotes": "single DingTalk openMessageId with unchanged value", "canonical_hint": "open-message-id", "members": ["msg-id", "message-id", "open-message-id"], "excludes": ["msg-ids", "message-ids", "open-message-ids", "ref-msg-id", "src-msg-id", "open-task-id", "topic-id", "resource-id"], "commands": ["chat +conversation-mark-read", "chat +flag-cancel", "chat +flag-create", "chat +messages-add-emoji", "chat +messages-add-text-emotion", "chat +messages-forward", "chat +messages-read-status", "chat +messages-remove-emoji", "chat +messages-remove-text-emotion", "chat +messages-resource-download", "chat +messages-set-pin", "chat +messages-set-top", "chat +messages-unset-pin", "chat +messages-unset-top", "chat mark-read", "chat message add-emoji", "chat message add-favorite", "chat message add-text-emotion", "chat message download-media", "chat message forward", "chat message read-status", "chat message recall", "chat message remove-emoji", "chat message remove-favorite", "chat message remove-text-emotion", "chat message set-pin-msg", "chat message set-top-msg", "chat message unset-pin-msg", "chat message unset-top-msg"], "risk": "yellow"},
"open_message_ids": {"denotes": "DingTalk openMessageId list with unchanged element values", "canonical_hint": "msg-ids", "members": ["msg-ids", "message-ids", "open-message-ids"], "excludes": ["msg-id", "message-id", "open-message-id", "ref-msg-id", "src-msg-id"], "commands": ["chat +flag-cancel", "chat +flag-create", "chat +messages-combine-forward", "chat +messages-mget", "chat message combine-forward", "chat message list-by-ids", "chat message list-emotion-replies"], "risk": "yellow"},
"referenced_open_message_id": {"denotes": "referenced DingTalk openMessageId in a reply", "canonical_hint": "ref-msg-id", "members": ["ref-msg-id", "ref-message-id"], "excludes": ["msg-id", "message-id", "open-message-id", "msg-ids", "src-msg-id"], "commands": ["chat +messages-reply", "chat message reply"], "risk": "yellow"},
"user_id": {"denotes": "single user id", "canonical_hint": "user-id", "members": ["user", "user-id", "userid", "uid", "staff-id"], "excludes": ["at-user-ids", "to-user", "users", "user-ids", "name"], "commands": ["chat +chat-role-query-user", "chat +chat-role-set-user", "chat +messages-list-direct", "chat chmod", "chat conversation-info", "chat group transfer-owner", "chat group-role query-user", "chat group-role remove-user", "chat group-role set-user", "chat message list", "chat message send", "contact user profile get"], "risk": "yellow"},
"user_ids": {"denotes": "user id list", "canonical_hint": "user-ids", "members": ["users", "user-ids"], "excludes": ["user", "user-id", "userid", "uid", "staff-id", "at-user-ids"], "commands": ["attendance +check-result", "attendance check result", "chat +messages-batch-send-by-bot", "chat group members remove", "chat group set-admin", "chat group-mute-member", "chat message read-status", "chat message search-advanced", "chat message send-by-bot"], "risk": "yellow"},
"open_dingtalk_ids": {"denotes": "DingTalk openDingTalkId list with unchanged element values", "canonical_hint": "open-dingtalk-ids", "members": ["open-dingtalk-ids"], "excludes": ["user", "user-id", "user-ids", "staff-id", "users"], "commands": ["chat +chat-members-get", "chat category create-smart", "chat group members list-by-ids", "chat message send-by-bot"], "risk": "yellow"},
"ding_id": {"denotes": "DING id", "canonical_hint": "ding-id", "members": ["ding-id", "open-ding-id"], "commands": ["ding message receiver-status"], "risk": "yellow"},
"folder_id": {"denotes": "drive folder id", "canonical_hint": "folder", "members": ["folder", "folder-id"], "excludes": ["space-id"], "commands": ["drive +copy", "drive +create-folder", "drive +create-shortcut", "drive +list", "drive +move", "drive +upload", "drive commit", "drive copy", "drive list", "drive mkdir", "drive move", "drive shortcut", "drive upload", "drive upload-info", "mail folder update"], "risk": "green"},
"drive_storage_space_id": {"denotes": "single numeric DingDrive storage space ID with unchanged value", "canonical_hint": "space-id", "members": ["space-id", "drive-space-id", "storage-space-id", "dingdrive-space-id"], "excludes": ["space", "workspace", "workspace-id", "knowledge-base-id", "wiki-workspace-id"], "commands": ["drive +create-folder", "drive +download", "drive +info", "drive +inspect", "drive +list", "drive +recycle-list", "drive +upload", "drive commit", "drive download", "drive info", "drive list", "drive mkdir", "drive recycle list", "drive upload", "drive upload-info"], "risk": "yellow"},
"app_id": {"denotes": "application id", "canonical_hint": "unified-app-id", "members": ["app-id", "unified-app-id", "application-id"], "excludes": ["app-key", "app-secret", "agent-id"], "commands": ["dev app get"], "risk": "yellow"},
"robot_code": {"denotes": "robot code", "canonical_hint": "robot-code", "members": ["robot-code", "robot"], "excludes": ["robot-id", "bot-id", "open-bot-id", "bot-code"], "commands": ["chat +chat-add-bot", "chat +messages-batch-recall-by-bot", "chat +messages-batch-send-by-bot", "chat +messages-recall-by-bot", "chat +messages-send-by-bot", "chat group members add-bot", "chat message recall-by-bot", "chat message send-by-bot", "ding message send"], "risk": "yellow"},
"open_bot_id": {"denotes": "single DingTalk openBotId with unchanged value", "canonical_hint": "bot-id", "members": ["bot-id", "open-bot-id"], "excludes": ["robot-code", "robot", "robot-id", "bot-code"], "commands": ["chat +chat-remove-bot", "chat group members remove-bot"], "risk": "yellow"},
"doc_node_id": {"denotes": "single DingTalk document nodeId or accepted document URL/token with unchanged value", "canonical_hint": "node", "members": ["node", "node-id", "doc", "doc-id", "file-id", "document-id", "url", "dentry-uuid"], "excludes": ["id", "folder", "folder-id", "parent-id", "workspace", "workspace-id", "block-id", "comment-id", "comment-key", "job-id", "task-id", "template-id", "version", "revision", "dentry-id", "space-id", "name", "role"], "commands": ["doc +access-change", "doc +access-grant", "doc +access-revoke", "doc +background-delete", "doc +background-update", "doc +checkpoint-update", "doc +comment-create", "doc +comment-delete", "doc +comment-list", "doc +comment-reply", "doc +comment-update", "doc +copy", "doc +doc-append", "doc +export", "doc +export-submit", "doc +fetch", "doc +history-list", "doc +history-revert", "doc +history-save", "doc +inspect", "doc +move", "doc +review", "doc +version-list", "doc +version-revert", "doc +version-save", "doc block delete", "doc block insert", "doc block list", "doc block update", "doc comment create", "doc comment create-inline", "doc comment delete", "doc comment list", "doc comment reply", "doc comment update", "doc export", "doc info", "doc media download", "doc media insert", "doc media upload", "doc read", "doc style background clear", "doc style background set", "doc style cover clear", "doc style cover set", "doc style get", "doc update", "doc version list", "doc version revert", "doc version save", "doc whiteboard insert"], "risk": "yellow"},
"doc_comment_key": {"denotes": "single DingTalk document commentKey with unchanged value", "canonical_hint": "comment-key", "members": ["comment-key", "comment-id"], "excludes": ["id", "node", "node-id", "doc-id", "block-id"], "commands": ["doc +comment-delete", "doc +comment-reply", "doc +comment-update", "doc comment delete", "doc comment reply", "doc comment update"], "risk": "yellow"},
"doc_version_number": {"denotes": "single document or Drive ordinary-file historical version number with unchanged positive integer value", "canonical_hint": "version", "members": ["version", "version-number", "version-no"], "excludes": ["revision", "id", "node", "node-id", "doc-id"], "commands": ["doc +history-revert", "doc +version-revert", "doc version revert", "drive +version-download", "drive +version-get", "drive +version-revert", "drive download", "drive download-version", "drive revert"], "risk": "yellow"},
"doc_content_format": {"denotes": "DingTalk document body format with unchanged markdown/jsonml value", "canonical_hint": "content-format", "members": ["content-format", "doc-format"], "excludes": ["format", "export-format", "mime-type"], "commands": ["doc +create", "doc +update", "doc create", "doc update"], "risk": "green"},
"doc_edit_revision": {"denotes": "single optimistic-concurrency revision for a document edit", "canonical_hint": "revision", "members": ["revision", "expected-revision"], "excludes": ["version", "version-number", "version-no"], "commands": ["doc +update", "doc update"], "risk": "yellow"},
"drive_recycle_item_id": {"denotes": "single Drive recycle-bin item ID returned by recycle list", "canonical_hint": "id", "members": ["id", "recycle-item-id", "trash-item-id", "deleted-item-id"], "excludes": ["node", "node-id", "file-id", "folder-id", "space-id", "workspace-id"], "commands": ["drive +recycle-restore", "drive recycle restore"], "risk": "yellow"},
"drive_modified_time_start": {"denotes": "Drive search modified-time lower bound in unchanged millisecond timestamp unit", "canonical_hint": "modified-from", "members": ["modified-from", "modified-after", "modify-time-from", "modified-time-start"], "excludes": ["modified-to", "created-from", "created-to", "start", "from"], "commands": ["drive +search", "drive search"], "risk": "yellow"},
"drive_modified_time_end": {"denotes": "Drive search modified-time upper bound in unchanged millisecond timestamp unit", "canonical_hint": "modified-to", "members": ["modified-to", "modified-before", "modify-time-to", "modified-time-end"], "excludes": ["modified-from", "created-from", "created-to", "end", "to"], "commands": ["drive +search", "drive search"], "risk": "yellow"},
"drive_file_size_bytes": {"denotes": "file size in bytes passed unchanged", "canonical_hint": "file-size", "members": ["file-size", "file-size-bytes", "size-bytes", "content-length"], "excludes": ["part-size", "page-size", "limit", "size"], "commands": ["drive commit", "drive upload-info"], "risk": "yellow"},
"drive_sort_direction": {"denotes": "Drive result sort direction with unchanged asc/desc enum", "canonical_hint": "order", "members": ["order", "sort", "sort-direction", "order-direction"], "excludes": ["order-by", "sort-by", "order-field"], "commands": ["drive +list", "drive list", "drive star list"], "risk": "green"},
"workspace_id": {"denotes": "single knowledge-base or document-space workspace ID/URL passed unchanged; never a numeric DingDrive storage space ID", "canonical_hint": "workspace", "members": ["workspace", "workspace-id", "knowledge-base-id", "wiki-workspace-id"], "excludes": ["space", "space-id", "drive-space-id", "storage-space-id", "dingdrive-space-id", "folder", "folder-id", "node", "node-id", "dentry-id"], "commands": ["doc +access-change", "doc +access-grant", "doc +access-revoke", "doc +copy", "doc +create", "doc +create-from-template", "doc +grant-and-share", "doc +import", "doc +list", "doc +move", "doc create", "doc file create", "doc import", "doc template apply", "drive +copy", "drive +create-shortcut", "drive +move", "drive copy", "drive list", "drive move", "drive permission add", "drive permission list", "drive permission remove", "drive permission transfer-owner", "drive permission update", "drive shortcut", "drive upload"], "risk": "yellow"},
"created_time_start": {"denotes": "Doc/Drive search created-time lower bound in unchanged millisecond timestamp unit", "canonical_hint": "created-from", "members": ["created-from", "created-after", "create-time-from", "created-time-start", "create-time-start"], "excludes": ["created-to", "modified-from", "modified-to", "start", "from"], "commands": ["doc +search", "drive +search", "drive search"], "risk": "yellow"},
"created_time_end": {"denotes": "Doc/Drive search created-time upper bound in unchanged millisecond timestamp unit", "canonical_hint": "created-to", "members": ["created-to", "created-before", "create-time-to", "created-time-end", "create-time-end"], "excludes": ["created-from", "modified-from", "modified-to", "end", "to"], "commands": ["doc +search", "drive +search", "drive search"], "risk": "yellow"},
"document_permission_role": {"denotes": "direct document-space permission role on grant/apply/update, passed unchanged", "canonical_hint": "role", "members": ["role", "permission-role", "access-role", "member-role"], "excludes": ["filter-role", "reserve-role", "permission", "public-permission"], "commands": ["doc +access-change", "doc +access-grant", "doc +grant-and-share", "drive permission add", "drive permission apply", "drive permission update"], "risk": "yellow"},
"creator_user_ids": {"denotes": "creator userId list used to filter Doc/Drive search results, passed unchanged", "canonical_hint": "creator-uids", "members": ["creator-uids", "creator-user-ids", "creator-ids", "created-by-user-ids"], "excludes": ["user", "user-id", "user-ids", "users", "owner-id", "modifier-uids"], "commands": ["doc +search", "drive +search", "drive search"], "risk": "yellow"},
"local_output_path": {"denotes": "local destination file or directory path for a download/export result, passed unchanged", "canonical_hint": "output", "members": ["output", "output-path", "destination-path", "save-path"], "excludes": ["file", "file-path", "folder", "folder-id", "content-file"], "commands": ["doc +export", "doc +export-get", "doc +media-download", "doc +resource-download", "doc read", "drive +download", "drive +version-download", "drive download", "drive download-version"], "risk": "green"}
},
"command_overrides": {
"doc +version-list": {"ambiguous": ["size", "max-results", "max-result", "take", "top", "per-page", "next-cursor", "next-token", "next-page-token"], "note": "--limit/--cursor and the shipped visible compatibility flags --page-size/--page-token remain native. Other pagination spellings cannot choose between two visible real flags and must stop before execution."},
"chat group rename": {"bind": {"id": "open_conversation_id"}, "note": "This command's real --id carries one openConversationId; aliases reduce to --id without changing the value."},
"chat group members": {"bind": {"id": "open_conversation_id"}},
"chat group members add": {"bind": {"id": "open_conversation_id"}, "block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a list and may contain mixed userId/openDingTalkId values; singular inputs are not promoted automatically."},
"chat group members remove": {"bind": {"id": "open_conversation_id"}},
"chat message add-emoji": {"scoped_aliases": {"chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat message add-text-emotion": {"scoped_aliases": {"chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat message remove-emoji": {"scoped_aliases": {"chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat message remove-text-emotion": {"scoped_aliases": {"chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat mute": {"scoped_aliases": {"group": "conversation-id", "chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --conversation-id/--id/--chat remain unchanged; other reviewed openConversationId spellings reduce to --conversation-id."},
"drive list": {"ambiguous": ["root-id", "space"], "note": "Numeric --space-id and knowledge-base --workspace are distinct routes; bare --space/--root-id cannot select a domain or folder.", "scoped_aliases": {"document-id": "node", "dentry-uuid": "node", "directory-id": "folder", "order-field": "order-by", "sort-by": "order-by", "sort-field": "order-by"}, "scope_strict": true, "block": ["dentry-id"]},
"drive upload": {"ambiguous": ["destination-id", "space", "target-id"], "note": "Local file, display name, MIME, overwrite node, folder, storage space, and knowledge-base workspace remain distinct roles.", "scoped_aliases": {"dentry-uuid": "node", "directory-id": "folder", "overwrite-node-id": "node", "target-folder-id": "folder", "target-workspace-id": "workspace", "source-file": "file", "content-type": "mime-type", "filename": "file-name", "name": "file-name", "display-name": "file-name", "upload-name": "file-name"}, "block": ["dentry-id", "document-url", "output-path"], "scope_strict": true},
"ding +receiver-status": {"scoped_aliases": {"id": "ding-id"}, "note": "generic id reduces to ding-id"},
"ding message receiver-status": {"scoped_aliases": {"id": "ding-id"}},
"contact user profile get": {"scoped_aliases": {"id": "staff-id", "ids": "staff-id"}, "note": "user-id is reduced by the user_id concept; generic id/ids bound explicitly"},
"mail folder update": {"bind": {"id": "folder_id"}, "note": "this command's --id is the folder id; --folder-id reduces to --id"},
"mail message search": {"scoped_aliases": {"subject": "query"}, "scope_strict": true, "note": "never globalize: mail template create has a real and different --subject"},
"calendar event list": {"scoped_aliases": {"date": "start"}, "note": "reviewed against ParseISOTimeToMillis and final list_calendar_events payload; --date is normalized centrally while the command's existing hidden compatibility flags remain native fallbacks"},
"chat +bot-find": {"scoped_aliases": {"name": "query"}, "scope_strict": true, "note": "On this exact bot-search shortcut, --name and --query denote the same search keyword; --name must not become a global search alias."},
"chat +chat-messages": {"scoped_aliases": {"chat": "group"}, "scope_strict": true, "note": "Evaluation compatibility: --chat carries one stable openConversationId and normalizes to the existing --group identifier route."},
"chat +search-msg": {"scoped_aliases": {"chat": "group"}, "scope_strict": true, "note": "Evaluation compatibility: scalar --chat carries one stable openConversationId and normalizes to the existing scalar --group filter."},
"chat bot find": {"scoped_aliases": {"name": "query"}, "scope_strict": true, "note": "On this exact bot-search command, --name and --query denote the same search keyword; --name must not become a global search alias."},
"chat +bot-search": {"scoped_aliases": {"query": "name", "current-page": "page"}, "block": ["cursor"], "scope_strict": true, "note": "Only the reviewed bot keyword and page-number spellings are accepted; cursor pagination cannot be converted to a page number."},
"chat bot search": {"scoped_aliases": {"query": "name", "current-page": "page"}, "block": ["cursor"], "scope_strict": true, "note": "Only the reviewed bot keyword and page-number spellings are accepted; cursor pagination cannot be converted to a page number."},
"chat message list-favorites": {"scoped_aliases": {"limit": "size"}, "scope_strict": true, "note": "On this exact command, both names denote the same bounded result count; the numeric value is unchanged."},
"chat +messages-list-unread-conversations": {"scoped_aliases": {"limit": "count", "size": "count"}, "scope_strict": true, "note": "On this exact command, limit and size both denote the returned unread-conversation count."},
"chat +unread-chats": {"scoped_aliases": {"limit": "count", "size": "count"}, "scope_strict": true, "note": "On this exact command, limit and size both denote the returned unread-conversation count."},
"chat message list-unread-conversations": {"scoped_aliases": {"limit": "count", "size": "count"}, "scope_strict": true, "note": "On this exact command, limit and size both denote the returned unread-conversation count."},
"chat +messages-list-direct": {"scoped_aliases": {"start": "time"}, "block": ["end"], "scope_strict": true, "note": "This exact command accepts one start boundary in yyyy-MM-dd HH:mm:ss; an end-only input cannot be represented."},
"chat message list": {"scoped_aliases": {"start": "time"}, "block": ["end"], "scope_strict": true, "note": "This exact command accepts one start boundary in yyyy-MM-dd HH:mm:ss; an end-only input cannot be represented."},
"chat message list-by-sender": {"scoped_aliases": {"user-id": "sender-user-id", "open-dingtalk-id": "sender-open-dingtalk-id"}, "block": ["time"], "scope_strict": true, "note": "Only same-role sender identifiers are mapped; --time cannot supply the required RFC3339 start/end range."},
"contact +resolve-dept": {"bind": {"name": "search_query"}, "note": "The real --name is a department-name search keyword and carries the search_query concept on this shortcut."},
"contact +list-sub-depts": {"block": ["name", "query"], "note": "--dept is an integer department id; names and search queries require a separate resolution command"},
"contact +dept-members": {"bind": {"dept": "search_query"}, "scoped_aliases": {"name": "dept"}, "note": "The real --dept is a department-name search keyword; search spellings come from search_query, while --name remains command-scoped."},
"chat message send": {"scoped_aliases": {"to-user": "user", "file": "file-path"}, "note": "Recipient and local-file-path aliases are exact to this command; obsolete file metadata flags remain unsupported."},
"chat +group-members": {"bind": {"group": "group_name"}, "note": "The real --group is a group-name search keyword on this shortcut, not an identifier."},
"chat +category-create": {"scoped_aliases": {"name": "title"}, "scope_strict": true, "note": "The reviewed name/title mapping preserves the category display-name value on this exact shortcut."},
"chat category create": {"scoped_aliases": {"name": "title"}, "scope_strict": true, "note": "The reviewed name/title mapping preserves the category display-name value on this exact command."},
"chat +category-rename": {"scoped_aliases": {"name": "title"}, "block": ["category-ids"], "scope_strict": true, "note": "The display-name alias is exact; a category-id list is not accepted where one category is required."},
"chat category rename": {"scoped_aliases": {"name": "title"}, "block": ["category-ids"], "scope_strict": true, "note": "The display-name alias is exact; a category-id list is not accepted where one category is required."},
"chat +category-delete": {"block": ["category-ids"], "note": "This command requires one category id; list cardinality is not reduced automatically."},
"chat category delete": {"block": ["category-ids"], "note": "This command requires one category id; list cardinality is not reduced automatically."},
"chat category list-conversations": {"block": ["category-ids"], "note": "This command requires one category id; list cardinality is not reduced automatically."},
"chat category add-conv": {"block": ["category-id"], "note": "This command requires a category-id list; one id is not promoted into a batch input."},
"chat category remove-conv": {"block": ["category-id"], "note": "This command requires a category-id list; one id is not promoted into a batch input."},
"chat +chat-role-update": {"block": ["role-ids"], "note": "This command requires one role id; list cardinality is not reduced automatically."},
"chat group-role remove": {"block": ["role-ids"], "note": "This command requires one role id; list cardinality is not reduced automatically."},
"chat group-role update": {"block": ["role-ids"], "note": "This command requires one role id; list cardinality is not reduced automatically."},
"chat +chat-role-set-user": {"block": ["role-id"], "note": "This command requires a role-id list; one id is not promoted into a batch input."},
"chat group-role remove-user": {"block": ["role-id"], "note": "This command requires a role-id list; one id is not promoted into a batch input."},
"chat group-role set-user": {"block": ["role-id"], "note": "This command requires a role-id list; one id is not promoted into a batch input."},
"chat +messages-send-by-webhook": {"scoped_aliases": {"at-user-ids": "at-users"}, "scope_strict": true, "note": "Both names denote the same userId list used for @ mentions on this exact shortcut."},
"chat message send-by-webhook": {"scoped_aliases": {"at-user-ids": "at-users"}, "scope_strict": true, "note": "Both names denote the same userId list used for @ mentions on this exact command."},
"doc block insert": {"block": ["before-block-id"], "note": "Parent and reference roles remain distinct. --before-block-id needs both --ref-block and --where before, while role-free --block-id cannot choose parent versus reference.", "scoped_aliases": {"parent-block-id": "parent-block", "ref-block-id": "ref-block", "reference-block-id": "ref-block"}, "ambiguous": ["block-id"], "scope_strict": true},
"chat message send-by-bot": {"scoped_aliases": {"at-users": "at-user-ids"}, "block": ["user-id", "to-user-id"], "ambiguous": ["at-ids"], "note": "The reviewed @ userId-list alias is exact; singular recipients are not promoted, and bare --at-ids cannot choose an identifier domain."},
"doc +export-get": {"block": ["doc-id", "document-id", "file-id", "node", "node-id", "task-id", "url"], "note": "This command queries one export jobId. Document node identifiers and import taskId spellings are different entities and are rejected.", "scoped_aliases": {"export-job-id": "job-id"}, "scope_strict": true},
"doc block delete": {"block": ["index"], "note": "index (position) vs node (node id) are different"},
"doc +copy": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder", "space": "workspace", "space-id": "workspace"}, "block": ["parent-id"], "scope_strict": true, "note": "This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId. The command-scoped --space/--space-id aliases preserve the compatibility published before workspace and numeric DingDrive storage-space concepts were split; they do not make those value domains globally equivalent."},
"doc +list": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder", "space": "workspace", "space-id": "workspace"}, "block": ["parent-id"], "scope_strict": true, "note": "This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId. The command-scoped --space/--space-id aliases preserve the compatibility published before workspace and numeric DingDrive storage-space concepts were split; they do not make those value domains globally equivalent."},
"doc +move": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder", "space": "workspace", "space-id": "workspace"}, "block": ["parent-id"], "scope_strict": true, "note": "This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId. The command-scoped --space/--space-id aliases preserve the compatibility published before workspace and numeric DingDrive storage-space concepts were split; they do not make those value domains globally equivalent."},
"doc comment create": {"scoped_aliases": {"mentioned-open-conversation-ids": "mentioned-open-conversation-id", "open-conversation-id": "mentioned-open-conversation-id", "open-conversation-ids": "mentioned-open-conversation-id"}, "block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids"], "scope_strict": true, "note": "The target is a list of mentioned openConversationIds. Explicit open-conversation spellings preserve the list value; numeric groupId and role-free conversation spellings are rejected."},
"doc comment reply": {"scoped_aliases": {"mentioned-open-conversation-ids": "mentioned-open-conversation-id", "open-conversation-id": "mentioned-open-conversation-id", "open-conversation-ids": "mentioned-open-conversation-id"}, "block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids"], "scope_strict": true, "note": "The target is a list of mentioned openConversationIds. Explicit open-conversation spellings preserve the list value; numeric groupId and role-free conversation spellings are rejected."},
"doc comment update": {"scoped_aliases": {"mentioned-open-conversation-ids": "mentioned-open-conversation-id", "open-conversation-id": "mentioned-open-conversation-id", "open-conversation-ids": "mentioned-open-conversation-id"}, "block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids"], "scope_strict": true, "note": "The target is a list of mentioned openConversationIds. Explicit open-conversation spellings preserve the list value; numeric groupId and role-free conversation spellings are rejected."},
"doc +comment-create": {"block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids", "mentioned-open-conversation-id", "mentioned-open-conversation-ids", "open-conversation-id", "open-conversation-ids"], "note": "This shortcut has no group-mention flag or payload field. Group-conversation spellings are blocked instead of inventing unsupported capability; route to the stable doc comment command when group mentions are required."},
"doc +comment-reply": {"block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids", "mentioned-open-conversation-id", "mentioned-open-conversation-ids", "open-conversation-id", "open-conversation-ids"], "note": "This shortcut has no group-mention flag or payload field. Group-conversation spellings are blocked instead of inventing unsupported capability; route to the stable doc comment command when group mentions are required."},
"doc media insert": {"scoped_aliases": {"ref-block-id": "ref-block", "reference-block-id": "ref-block"}, "block": ["before-block-id", "parent-block", "parent-block-id"], "ambiguous": ["block-id"], "scope_strict": true, "note": "Media insertion supports a reference block but no parent-block role. --before-block-id additionally needs --where before; role-free --block-id is left ambiguous."},
"doc read": {"block": ["before-block-id", "parent-block-id", "ref-block-id", "reference-block-id"], "ambiguous": ["block-id"], "note": "A section read requires --scope section plus a start/end boundary. A role-free --block-id cannot be reduced to one flag without inventing the missing scope/boundary role."},
"doc export get": {"scoped_aliases": {"export-job-id": "job-id"}, "block": ["doc-id", "document-id", "file-id", "node", "node-id", "url"], "scope_strict": true, "note": "This command queries one export jobId. Document node identifiers are rejected; native hidden --task-id remains the command's reviewed add-only compatibility alias for --job-id."},
"doc import get": {"scoped_aliases": {"import-task-id": "task-id"}, "block": ["doc-id", "document-id", "file-id", "job-id", "node", "node-id", "url"], "scope_strict": true, "note": "This command queries one import taskId. Document node identifiers and export jobId spellings are different entities and are rejected."},
"doc +share-doc": {"block": ["doc", "doc-id", "document-id", "file-id", "id", "node", "node-id"], "note": "The real --url requires a shareable document link. Name-only normalization cannot turn a document nodeId into a URL, so identifier spellings are rejected with guidance to provide --url."},
"doc update": {"block": ["stdin", "version", "version-no", "version-number"], "note": "--revision is an optimistic-concurrency revision, not a historical document version number. Version spellings must not reduce to --revision. --stdin is not a real switch: stdin input is expressed as --content -, which requires a value-form transformation outside central name aliases."},
"report outbox list": {"block": ["template-type"], "note": "type vs name are different fields"},
"chat group members add-bot": {"bind": {"id": "open_conversation_id"}},
"chat group members list-by-ids": {"bind": {"id": "open_conversation_id", "users": "open_dingtalk_ids"}, "block": ["user-id", "user-ids"], "note": "This command's --id carries openConversationId, while --users carries an openDingTalkId list."},
"chat group members remove-bot": {"bind": {"id": "open_conversation_id"}},
"chat +send-to-group": {"bind": {"group": "group_name"}, "note": "The real --group is a group-name search keyword on this shortcut, not an identifier."},
"chat group share-invite": {"scoped_aliases": {"source-conversation-id": "source", "target-conversation-id": "target"}, "block": ["group-id", "group-ids", "user", "user-id", "userid", "uid", "staff-id"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "note": "A role-free conversation identifier cannot choose between source and target; --receiver requires openDingTalkId and must not accept userId spellings."},
"chat message combine-forward": {"scoped_aliases": {"src-open-cid": "src-conversation-id", "dest-open-cid": "dest-conversation-id", "source-conversation-id": "src-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id"}, "block": ["group-id", "group-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "note": "Source and destination conversation roles are preserved; a role-free identifier is ambiguous."},
"chat message forward": {"scoped_aliases": {"src-open-cid": "src-conversation-id", "dest-open-cid": "dest-conversation-id", "source-conversation-id": "src-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id"}, "block": ["group-id", "group-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "note": "Source and destination conversation roles are preserved; a role-free identifier is ambiguous."},
"chat message forward-topic": {"scoped_aliases": {"src-open-conversation-id": "src-conversation-id", "dest-open-conversation-id": "dest-conversation-id", "source-conversation-id": "src-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id", "src-open-message-id": "src-msg-id", "source-message-id": "src-msg-id"}, "block": ["group-id", "group-ids", "msg-id", "message-id", "open-message-id"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "note": "Conversation and message source/destination roles are preserved; role-free identifiers are rejected."},
"chat +conversation-info": {"block": ["user", "user-id", "userid", "uid", "staff-id"], "note": "This shortcut accepts --open-dingtalk-id, not userId; use stable chat conversation-info when userId resolution is needed."},
"chat group create": {"block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a list and may contain mixed identifier domains."},
"chat +chat-set-admin": {"block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a mixed userId/openDingTalkId list."},
"chat +messages-read-status": {"block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a mixed userId/openDingTalkId list."},
"chat category create-smart": {"bind": {"members": "open_dingtalk_ids"}, "scoped_aliases": {"title": "name"}, "note": "The real --members is an openDingTalkId list; the reviewed title/name alias is exact to the category display name."},
"chat group audit-join-validation": {"ambiguous": ["user", "user-id", "userid", "uid", "staff-id"], "note": "A role-free user identifier cannot choose between the required --applicant and --inviter roles."},
"chat message reply": {"block": ["user", "user-id", "userid", "uid", "staff-id"], "note": "The required --ref-sender is a role-specific openDingTalkId and must not accept generic userId spellings."},
"chat message send-card": {"block": ["user", "user-id", "userid", "uid", "staff-id"], "note": "The real --receiver is a role-specific openDingTalkId and must not accept generic userId spellings."},
"chat +category-add-conversation": {"block": ["category-id"], "note": "The real --category-ids is a list; a singular category ID is not promoted automatically."},
"chat +category-list-conversations": {"block": ["category-ids"], "note": "The real --category-id is singular; list cardinality is not reduced automatically."},
"chat +category-remove-conversation": {"block": ["category-id"], "note": "The real --category-ids is a list; a singular category ID is not promoted automatically."},
"chat +chat-add-bot": {"bind": {"id": "open_conversation_id"}, "note": "The real --id is the group's openConversationId; robotCode and openBotId remain different domains."},
"chat +chat-audit-join": {"scoped_aliases": {"applicant-user-id": "applicant", "inviter-user-id": "inviter"}, "ambiguous": ["user", "user-id", "userid", "uid", "staff-id"], "scope_strict": true, "note": "A role-free user identifier cannot choose between applicant and inviter."},
"chat +chat-create": {"block": ["user-id", "open-dingtalk-id"], "note": "The real --users field is a list and may contain mixed identifier domains; a singular value is not promoted."},
"chat +chat-get-by-id": {"block": ["group", "conversation-id", "chat", "chat-id", "open-conversation-id", "conversation-ids", "open-conversation-ids", "group-name", "name", "id"], "note": "The real --group-id is numeric groupId; no CID or group-name spelling can be value-preservingly converted."},
"chat +chat-members-get": {"bind": {"id": "open_conversation_id", "users": "open_dingtalk_ids"}, "block": ["group", "group-name", "user-id", "user-ids"], "note": "The real --id is openConversationId and --users is an openDingTalkId list. The observed --group spelling carried a natural group name and is blocked; explicit CID spellings and --chat remain value-preserving aliases."},
"chat +chat-members-list": {"scoped_aliases": {"chat-id": "conversation-id", "id": "conversation-id"}, "block": ["query", "keyword", "group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "scope_strict": true, "note": "Native --chat/--open-conversation-id stay native; member filtering by query is unsupported and group-name resolution stays on --group/--chat-query."},
"chat +chat-mute-member": {"scoped_aliases": {"user-ids": "users", "open-dingtalk-ids": "users"}, "block": ["user", "user-id", "open-dingtalk-id"], "scope_strict": true, "note": "The target accepts a mixed identifier list; list spellings preserve values, but singular inputs are not promoted."},
"chat +chat-remove-bot": {"bind": {"id": "open_conversation_id"}, "note": "The real --id is openConversationId; --bot-id is separately governed by open_bot_id."},
"chat +chat-role-remove": {"block": ["role-ids"], "note": "The command removes one role ID; list cardinality is not reduced."},
"chat +chat-role-remove-user": {"scoped_aliases": {"user-id": "user", "open-dingtalk-id": "user"}, "block": ["role-id"], "scope_strict": true, "note": "The single --user accepts either identifier domain; --role-ids remains a list."},
"chat +chat-transfer-owner": {"scoped_aliases": {"user-id": "new-owner", "open-dingtalk-id": "new-owner"}, "scope_strict": true, "note": "The only user role is the new owner, and the target accepts either userId or openDingTalkId without changing the value."},
"chat +chat-update": {"scoped_aliases": {"conversation-id": "group", "open-conversation-id": "group", "chat-id": "group", "title": "name", "new-title": "name"}, "block": ["id", "group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "scope_strict": true, "note": "--group accepts a name or CID, so only explicit CID spellings are mapped; generic --id is blocked."},
"chat +conversation-set-top": {"scoped_aliases": {"open-conversation-id": "conversation-id", "chat-id": "conversation-id", "open-conversation-ids": "conversation-ids", "chat-ids": "conversation-ids"}, "block": ["group", "groups", "group-id", "group-ids", "top", "set-top"], "scope_strict": true, "note": "Singular/list cardinality stays explicit; top/set-top cannot be rewritten to the inverse --off switch."},
"chat +feed-group-query-item": {"scoped_aliases": {"open-conversation-ids": "conversation-ids", "chat-ids": "conversation-ids"}, "block": ["group", "groups", "group-id", "group-ids", "conversation-id", "open-conversation-id"], "scope_strict": true, "note": "The real field is an openConversationId list; group names and singular IDs are not converted."},
"chat +flag-list": {"scoped_aliases": {"limit": "page-size"}, "block": ["max", "max-results", "max-size", "count", "page", "per-page"], "scope_strict": true, "note": "Native --page-size is the canonical page bound, native --size is its command-owned compatibility alias, and --limit is reviewed as value-preservingly equivalent to --page-size; total-count and page-number spellings are not equivalent."},
"chat +messages-batch-recall-by-bot": {"block": ["msg-id", "message-id", "open-message-id", "msg-ids", "message-ids", "open-message-ids"], "note": "--keys carries processQueryKey values returned by bot sending; it is not an openMessageId field."},
"chat +messages-combine-forward": {"scoped_aliases": {"src-open-cid": "src-conversation-id", "src-open-conversation-id": "src-conversation-id", "source-conversation-id": "src-conversation-id", "dest-open-cid": "dest-conversation-id", "dest-open-conversation-id": "dest-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id"}, "block": ["group-id", "group-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "scope_strict": true, "note": "Source and destination conversation roles remain explicit; role-free CID spellings cannot choose a side."},
"chat +messages-forward": {"scoped_aliases": {"src-open-cid": "src-conversation-id", "src-open-conversation-id": "src-conversation-id", "source-conversation-id": "src-conversation-id", "dest-open-cid": "dest-conversation-id", "dest-open-conversation-id": "dest-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id", "src-open-message-id": "msg-id", "source-message-id": "msg-id"}, "block": ["group-id", "group-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "scope_strict": true, "note": "The message role is uniquely the source message, but source/destination conversation roles cannot be inferred from a generic CID."},
"chat +messages-forward-topic": {"scoped_aliases": {"src-open-conversation-id": "src-conversation-id", "source-conversation-id": "src-conversation-id", "dest-open-conversation-id": "dest-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id", "src-open-message-id": "src-msg-id", "source-message-id": "src-msg-id"}, "block": ["group-id", "group-ids", "msg-id", "message-id", "open-message-id", "msg-ids", "message-ids", "open-message-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "scope_strict": true, "note": "Source and destination conversation roles and the source-message role remain explicit; role-free message IDs and list cardinality are not inferred."},
"chat +messages-list": {"scoped_aliases": {"start": "time"}, "block": ["before", "before-time", "end", "direction", "page-all", "count", "max", "max-results", "max-size", "page-size"], "scope_strict": true, "note": "start preserves the same boundary value; before/direction require multi-parameter or value transforms and page-all requires iteration. Native --conversation-id/--id/--size remain native."},
"chat +messages-recall-by-bot": {"block": ["msg-id", "message-id", "open-message-id", "msg-ids", "message-ids", "open-message-ids"], "note": "--keys carries processQueryKey values, not openMessageId values."},
"chat +messages-reply": {"scoped_aliases": {"msg-id": "ref-msg-id", "open-message-id": "ref-msg-id"}, "block": ["group", "msg-ids", "message-ids", "open-message-ids"], "scope_strict": true, "note": "The observed --group spelling carried a natural group name and is blocked. The only message role is the referenced message; plural IDs are not accepted, while --chat remains a CID alias and native --message-id stays native."},
"chat +messages-resource-download": {"block": ["download-dir"], "note": "--output may be a file or directory under workspace safety rules; a download directory cannot be assumed equivalent."},
"doc +access-change": {"scoped_aliases": {"space": "workspace", "space-id": "workspace"}, "scope_strict": true, "note": "Command-scoped compatibility published before the workspace/storage-space concept split: these spellings keep passing their value unchanged to this Doc command's --workspace flag and do not establish global equivalence with a numeric DingDrive storage space."},
"doc +access-grant": {"scoped_aliases": {"space": "workspace", "space-id": "workspace"}, "block": ["target-user-id", "target-user-ids", "user-id", "user-ids"], "ambiguous": ["target-user", "user", "users"], "scope_strict": true, "note": "The real --to accepts collaborator names and resolves them before granting access. Explicit ID spellings cannot be passed through unchanged, while role-free user spellings do not prove whether their values are names or IDs. The command-scoped --space/--space-id aliases preserve previously published Doc workspace compatibility without making workspace and numeric DingDrive storage-space values globally equivalent."},
"doc +access-revoke": {"scoped_aliases": {"space": "workspace", "space-id": "workspace"}, "scope_strict": true, "note": "Command-scoped compatibility published before the workspace/storage-space concept split: these spellings keep passing their value unchanged to this Doc command's --workspace flag and do not establish global equivalence with a numeric DingDrive storage space."},
"doc +create": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder", "space": "workspace", "space-id": "workspace"}, "block": ["content-file", "parent-id"], "scope_strict": true, "note": "--content-format is value-preservingly normalized to --doc-format. A raw --content-file path cannot become --content without adding the required @file transform, so it is blocked with guidance to use @relative-path or stable doc create. The command-scoped --space/--space-id aliases preserve previously published Doc workspace compatibility without making workspace and numeric DingDrive storage-space values globally equivalent."},
"doc +create-from-template": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder", "space": "workspace", "space-id": "workspace"}, "block": ["parent-id"], "scope_strict": true, "note": "This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId. The command-scoped --space/--space-id aliases preserve previously published Doc workspace compatibility without making workspace and numeric DingDrive storage-space values globally equivalent."},
"doc +import": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder", "space": "workspace", "space-id": "workspace"}, "block": ["parent-id"], "scope_strict": true, "note": "This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId. The command-scoped --space/--space-id aliases preserve previously published Doc workspace compatibility without making workspace and numeric DingDrive storage-space values globally equivalent."},
"doc create": {"scoped_aliases": {"space": "workspace", "space-id": "workspace"}, "scope_strict": true, "note": "Command-scoped compatibility published before the workspace/storage-space concept split: these spellings keep passing their value unchanged to this Doc command's --workspace flag and do not establish global equivalence with a numeric DingDrive storage space."},
"doc file create": {"scoped_aliases": {"space": "workspace", "space-id": "workspace"}, "scope_strict": true, "note": "Command-scoped compatibility published before the workspace/storage-space concept split: these spellings keep passing their value unchanged to this Doc command's --workspace flag and do not establish global equivalence with a numeric DingDrive storage space."},
"doc import": {"scoped_aliases": {"space": "workspace", "space-id": "workspace"}, "scope_strict": true, "note": "Command-scoped compatibility published before the workspace/storage-space concept split: these spellings keep passing their value unchanged to this Doc command's --workspace flag and do not establish global equivalence with a numeric DingDrive storage space."},
"doc template apply": {"scoped_aliases": {"space": "workspace", "space-id": "workspace"}, "scope_strict": true, "note": "Command-scoped compatibility published before the workspace/storage-space concept split: these spellings keep passing their value unchanged to this Doc command's --workspace flag and do not establish global equivalence with a numeric DingDrive storage space."},
"doc +update": {"scoped_aliases": {"mode": "command", "doc-id": "node", "document-id": "node", "file-id": "node", "node-id": "node", "url": "node"}, "block": ["content-file"], "ambiguous": ["element"], "scope_strict": true, "note": "--mode append/overwrite is the same operation selector subset as --command and preserves its value. --content-file is blocked because +update requires @relative-path or stdin and central aliases cannot read/transform a file value. --element cannot choose between document content, a block target, or an insertion reference."},
"doc +inspect": {"scoped_aliases": {"include-access": "include-permissions", "include-member": "include-permissions", "include-members": "include-permissions", "include-versions": "include-history"}, "block": ["include", "include-blocks", "include-content", "include-info", "include-meta", "include-metadata"], "scope_strict": true, "note": "Access/member spellings denote the same optional permission list, and versions/history denote the same history section. Generic --include needs value-dependent expansion; base metadata is already returned; block/content reads belong to +fetch, so those spellings stop before fuzzy correction or dispatch."},
"doc +fetch": {"scoped_aliases": {"start-block": "start-block-id", "end-block": "end-block-id"}, "block": ["content-format", "doc-format", "range"], "ambiguous": ["block-id"], "scope_strict": true, "note": "Start/end block roles are preserved. A role-free --block-id cannot choose a range/section boundary. --range is an invented composite argument observed alongside --scope range and cannot be split into block IDs by name-only normalization; content-format spellings do not map to the independent --detail contract."},
"doc +export": {"block": ["wait"], "scope_strict": true, "note": "The shortcut already submits, polls, and downloads in one workflow. A generic --wait value cannot be converted into the distinct integer --max-polls contract by parameter-name normalization."},
"doc +media-download": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "This command has both document-node and attachment-resource roles. Strong document spellings map to --node; --file-id and --url cannot safely choose between document and media identities."},
"doc +media-insert": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "block": ["after-block-id", "before-block-id"], "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "This command has both document-node and local-media roles. Strong document spellings map to --node; --file-id and --url cannot safely choose a role. Before/after block spellings each require expansion into both --ref-block and --where, which name-only normalization cannot perform."},
"doc +media-list": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "The command lists media inside one document, so only strong document spellings map to --node. File and URL spellings remain role-ambiguous."},
"doc +media-preview": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "This command has both document-node and attachment-resource roles. Strong document spellings map to --node; --file-id and --url cannot safely choose a role."},
"doc +resource-delete": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "This command removes a document resource while targeting the document by --node. File and URL spellings do not uniquely identify that document role."},
"doc +resource-download": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "This command downloads a document resource while targeting the document by --node. File and URL spellings do not uniquely identify that document role."},
"doc +resource-update": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "node-id": "node"}, "ambiguous": ["file-id", "url"], "scope_strict": true, "note": "This command has document-node, local-file, and HTTPS image URL roles. Only strong document spellings map to --node; --file-id and --url must stop as ambiguous."},
"doc +share": {"block": ["doc", "doc-id", "document-id", "file-id", "id", "node", "node-id"], "note": "The real --url requires a shareable document link. Name-only normalization cannot turn a node identifier into a URL, so identifier spellings are rejected with guidance to provide --url."},
"doc +grant-and-share": {"scoped_aliases": {"doc": "node", "doc-id": "node", "document-id": "node", "file-id": "node", "node-id": "node", "space": "workspace", "space-id": "workspace"}, "scope_strict": true, "note": "This workflow has two different real URL roles: --node selects the document for access control and --url is the shareable link sent to recipients. Explicit document-ID spellings map only to --node; --url remains native. The command-scoped --space/--space-id aliases preserve previously published Doc workspace compatibility without making workspace and numeric DingDrive storage-space values globally equivalent."},
"doc +version-save": {"block": ["message", "title"], "scope_strict": true, "note": "The current snapshot API accepts only the document target. Version title/message metadata are unsupported and cannot be represented by another existing flag."},
"drive copy": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node", "directory-id": "folder", "source-node-id": "node", "source-file-id": "node", "target-folder-id": "folder", "destination-folder-id": "folder", "target-workspace-id": "workspace", "destination-workspace-id": "workspace"}, "scope_strict": true, "note": "Source node and destination folder/workspace are different roles; role-free target IDs stop before write dispatch. --name is not a rename field on this command and must not be fuzzy-corrected to --node.", "block": ["dentry-id", "destination-node-id", "dingdrive-space-id", "drive-space-id", "name", "source-folder-id", "space-id", "storage-space-id"], "ambiguous": ["destination-id", "target-id"]},
"drive cover": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node"}, "scope_strict": true, "note": "The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.", "block": ["dentry-id"]},
"drive download-version": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node", "chunk-size": "part-size", "concurrency": "parallel", "parallelism": "parallel"}, "scope_strict": true, "note": "Output path, chunk size, and concurrency names preserve values; local input and remote folder roles remain blocked.", "block": ["dentry-id", "file", "file-path", "folder", "folder-id"]},
"drive move": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node", "directory-id": "folder", "source-node-id": "node", "source-file-id": "node", "target-folder-id": "folder", "destination-folder-id": "folder", "target-workspace-id": "workspace", "destination-workspace-id": "workspace"}, "scope_strict": true, "note": "Source node and destination folder/workspace are different roles; role-free target IDs stop before write dispatch. --name is not a rename field on this command and must not be fuzzy-corrected to --node.", "block": ["dentry-id", "destination-node-id", "dingdrive-space-id", "drive-space-id", "name", "source-folder-id", "space-id", "storage-space-id"], "ambiguous": ["destination-id", "target-id"]},
"drive permission add": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node", "member-user-ids": "users", "collaborator-ids": "users", "target-user-ids": "users", "target-node-id": "node"}, "scope_strict": true, "note": "The user list denotes target collaborators on this exact node permission command; the native singular compatibility spellings remain native.", "block": ["dentry-id", "dingdrive-space-id", "drive-space-id", "space-id", "storage-space-id"]},
"drive permission apply": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node", "approver-user-ids": "users", "approver-ids": "users", "target-node-id": "node", "apply-reason": "reason", "notification-mode": "notify-mode"}, "scope_strict": true, "note": "The user list denotes approvers, not target collaborators; values and notification enum are passed unchanged.", "block": ["dentry-id"]},
"drive permission apply-info": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node"}, "scope_strict": true, "note": "The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.", "block": ["dentry-id"]},
"drive permission list": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node", "role-filter": "filter-role", "permission-role-filter": "filter-role", "target-node-id": "node"}, "scope_strict": true, "note": "Filtering by a role is not the same as granting/updating a role.", "block": ["dentry-id", "dingdrive-space-id", "drive-space-id", "permission", "role", "space-id", "storage-space-id"]},
"drive permission remove": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node", "member-user-ids": "users", "collaborator-ids": "users", "target-user-ids": "users", "target-node-id": "node"}, "scope_strict": true, "note": "The user list denotes target collaborators on this exact node permission command; the native singular compatibility spellings remain native.", "block": ["dentry-id", "dingdrive-space-id", "drive-space-id", "space-id", "storage-space-id"]},
"drive permission transfer-owner": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node", "target-node-id": "node", "target-workspace-id": "workspace", "old-owner-role": "reserve-role", "keep-role": "reserve-role", "new-owner-id": "new-owner", "new-owner-user-id": "new-owner"}, "scope_strict": true, "note": "Node/workspace target and new/old owner roles are distinct on this irreversible command; role-free names stop before dispatch.", "block": ["current-owner", "dentry-id", "dingdrive-space-id", "drive-space-id", "space-id", "storage-space-id", "user-ids", "users"], "ambiguous": ["owner", "owner-user-id", "target-id", "user-id"]},
"drive permission update": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node", "member-user-ids": "users", "collaborator-ids": "users", "target-user-ids": "users", "target-node-id": "node"}, "scope_strict": true, "note": "The user list denotes target collaborators on this exact node permission command; the native singular compatibility spellings remain native.", "block": ["dentry-id", "dingdrive-space-id", "drive-space-id", "space-id", "storage-space-id"]},
"drive publish get": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node"}, "scope_strict": true, "note": "The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.", "block": ["dentry-id"]},
"drive publish set": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node", "public-permission": "permission", "public-role": "permission"}, "scope_strict": true, "note": "Internet-public permission is not the same as a direct collaborator role.", "block": ["access-role", "dentry-id", "permission-role", "role"]},
"drive publish unset": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node"}, "scope_strict": true, "note": "The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.", "block": ["dentry-id"]},
"drive rename": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node", "file-name": "name", "display-name": "name", "new-name": "name"}, "scope_strict": true, "note": "The name is this exact folder/node display name; search query and local path are different roles.", "block": ["dentry-id"]},
"drive revert": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node"}, "scope_strict": true, "note": "The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.", "block": ["dentry-id"]},
"drive shortcut": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node", "directory-id": "folder", "source-node-id": "node", "source-file-id": "node", "target-folder-id": "folder", "destination-folder-id": "folder", "target-workspace-id": "workspace", "destination-workspace-id": "workspace"}, "scope_strict": true, "note": "Source node and destination folder/workspace are different roles; role-free target IDs stop before write dispatch. --name is not a rename field on this command and must not be fuzzy-corrected to --node.", "block": ["dentry-id", "destination-node-id", "dingdrive-space-id", "drive-space-id", "name", "source-folder-id", "space-id", "storage-space-id"], "ambiguous": ["destination-id", "target-id"]},
"drive star add": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node"}, "scope_strict": true, "note": "The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.", "block": ["dentry-id"]},
"drive star remove": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node"}, "scope_strict": true, "note": "The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.", "block": ["dentry-id"]},
"drive stats": {"scoped_aliases": {"document-id": "node", "dentry-uuid": "node"}, "scope_strict": true, "note": "The command accepts a Drive node ID or URL; native file-id/node-id/doc-id/url aliases stay native and are not duplicated centrally.", "block": ["dentry-id"]},
"drive +cover": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node", "doc-id": "node", "document-id": "node", "url": "node", "document-url": "node", "id": "node"}, "block": ["dentry-id", "doc", "folder", "folder-id", "name"], "scope_strict": true, "note": "This shortcut calls the same get_cover nodeId interface as drive cover, so its reviewed ID/URL aliases are preserved unchanged. Numeric dentryId, folder-role spellings and --name remain protected."},
"drive +copy": {"scoped_aliases": {"dentry-uuid": "node", "directory-id": "folder", "source-node-id": "node", "source-file-id": "node", "target-folder-id": "folder", "destination-folder-id": "folder", "target-workspace-id": "workspace", "destination-workspace-id": "workspace"}, "block": ["dentry-id", "destination-node-id", "dingdrive-space-id", "document-url", "drive-space-id", "name", "source-folder-id", "space-id", "storage-space-id"], "scope_strict": true, "note": "Source node and destination folder/workspace are different roles; role-free target IDs stop before write dispatch. --name is not a rename field on this command and must not be fuzzy-corrected to --node.", "ambiguous": ["destination-id", "target-id"]},
"drive +create-folder": {"scoped_aliases": {"directory-id": "folder", "parent-directory-id": "folder", "parent-folder-id": "folder", "folder-name": "name", "display-name": "name", "new-name": "name"}, "block": ["dentry-id", "parent-id", "knowledge-base-id", "wiki-workspace-id", "workspace", "workspace-id"], "scope_strict": true, "note": "The new shortcut creates a numeric DingDrive-space folder. Folder display name, parent dentryUuid and numeric storage space are separate roles; knowledge-base workspace spellings are not accepted."},
"drive +create-shortcut": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node", "doc-id": "node", "document-id": "node", "url": "node", "document-url": "node", "directory-id": "folder", "source-node-id": "node", "source-file-id": "node", "target-folder-id": "folder", "destination-folder-id": "folder", "target-workspace-id": "workspace", "destination-workspace-id": "workspace"}, "block": ["dentry-id", "destination-node-id", "dingdrive-space-id", "doc", "drive-space-id", "name", "source-folder-id", "space-id", "storage-space-id"], "scope_strict": true, "note": "Source node and destination folder/workspace are different roles. The source ID/URL aliases match drive shortcut and pass through unchanged; generic target/id spellings remain ambiguous, and storage-space IDs are not knowledge-base workspace IDs.", "ambiguous": ["destination-id", "id", "target-id"]},
"drive +delete": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node", "folder": "node", "folder-id": "node"}, "block": ["dentry-id", "doc", "doc-id", "document-id", "document-url", "id", "name", "url"], "scope_strict": true, "note": "Delete targets one already confirmed Drive file or folder dentryUuid. Folder spellings are the same single target role; numeric dentryId, unreviewed Doc URL spellings and --name stop before confirmation or write dispatch."},
"drive +download": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node"}, "block": ["dentry-id", "doc", "doc-id", "document-id", "document-url", "file", "file-path", "folder", "folder-id", "id", "knowledge-base-id", "name", "url", "wiki-workspace-id", "workspace", "workspace-id"], "scope_strict": true, "note": "The remote ordinary-file node and local output path are different roles. Node-ID spelling is accepted, while Doc URLs, local input paths, folders, knowledge-base workspaces and numeric dentryId values are not interchangeable."},
"drive +info": {"scoped_aliases": {"dentry-uuid": "node"}, "block": ["dentry-id", "document-url", "knowledge-base-id", "wiki-workspace-id", "workspace", "workspace-id"], "scope_strict": true, "note": "This command has only a numeric DingDrive space target; knowledge-base workspace spellings are a different value domain."},
"drive +inspect": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node", "doc-id": "node", "document-id": "node", "folder": "node", "folder-id": "node", "include-statistics": "include-stats", "include-publish-status": "include-publish", "include-public-status": "include-publish", "include-thumbnail": "include-cover"}, "block": ["dentry-id", "doc", "document-url", "id", "include", "include-content", "include-history", "include-permissions", "name", "url"], "scope_strict": true, "note": "Drive inspect accepts one file, folder or document node ID and can aggregate only stats, public-publish status and cover. URL spellings, Doc inspect section names and a generic --include remain protected because this shortcut does not declare URL input or value splitting."},
"drive +list": {"ambiguous": ["root-id", "space"], "scoped_aliases": {"directory-id": "folder", "parent-folder-id": "folder", "order-field": "order-by", "sort-by": "order-by", "sort-field": "order-by"}, "block": ["dentry-id", "knowledge-base-id", "wiki-workspace-id", "workspace", "workspace-id"], "scope_strict": true, "note": "This shortcut accepts one numeric DingDrive space and an optional parent dentryUuid; it does not accept a knowledge-base workspace. Sort field and direction remain separate roles."},
"drive +move": {"scoped_aliases": {"dentry-uuid": "node", "directory-id": "folder", "source-node-id": "node", "source-file-id": "node", "target-folder-id": "folder", "destination-folder-id": "folder", "target-workspace-id": "workspace", "destination-workspace-id": "workspace"}, "block": ["dentry-id", "destination-node-id", "dingdrive-space-id", "document-url", "drive-space-id", "name", "source-folder-id", "space-id", "storage-space-id"], "scope_strict": true, "note": "Source node and destination folder/workspace are different roles; role-free target IDs stop before write dispatch. --name is not a rename field on this command and must not be fuzzy-corrected to --node.", "ambiguous": ["destination-id", "target-id"]},
"drive +publish-get": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node", "doc-id": "node", "document-id": "node", "url": "node", "document-url": "node", "id": "node"}, "block": ["dentry-id", "doc", "folder", "folder-id", "name"], "scope_strict": true, "note": "The shortcut calls the same publication-status fileId interface as drive publish get, so its reviewed ID/URL aliases are preserved unchanged. Numeric dentryId and folder/name roles remain protected."},
"drive +publish-unset": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node", "doc-id": "node", "document-id": "node", "url": "node", "document-url": "node", "id": "node"}, "block": ["dentry-id", "doc", "folder", "folder-id", "name"], "scope_strict": true, "note": "The shortcut calls the same set_file_publish fileId interface as drive publish unset, so its reviewed ID/URL aliases are preserved unchanged. Numeric dentryId and folder/name roles remain protected before confirmation."},
"drive +recycle-restore": {"bind": {"id": "drive_recycle_item_id"}, "block": ["file-id", "folder-id", "node", "node-id", "space-id", "workspace-id"], "scope_strict": true, "note": "The real --id is a recycleItemId returned by drive +recycle-list, not a normal Drive node ID."},
"drive +rename": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node", "doc-id": "node", "document-id": "node", "url": "node", "document-url": "node", "id": "node", "folder": "node", "folder-id": "node", "file-name": "name", "display-name": "name", "new-name": "name"}, "block": ["dentry-id"], "scope_strict": true, "note": "The existing document, file or folder node and the requested new display name are separate required roles. Node ID/URL aliases match drive rename and pass through unchanged; numeric dentryId remains protected."},
"drive delete": {"scoped_aliases": {"dentry-uuid": "node"}, "block": ["dentry-id", "document-url"], "scope_strict": true, "note": "This command publicly accepts an ID-only Drive node; dentry spellings preserve the value and URL-specific spellings remain protected."},
"drive download": {"scoped_aliases": {"dentry-uuid": "node", "chunk-size": "part-size", "concurrency": "parallel", "parallelism": "parallel"}, "block": ["dentry-id", "document-url", "file", "file-path", "folder", "folder-id", "knowledge-base-id", "wiki-workspace-id", "workspace", "workspace-id"], "scope_strict": true, "note": "Output path, chunk size, and concurrency names preserve values; local input and remote folder roles remain blocked."},
"drive info": {"scoped_aliases": {"dentry-uuid": "node", "space": "space-id", "workspace": "space-id", "workspace-id": "space-id"}, "block": ["dentry-id", "document-url", "knowledge-base-id", "wiki-workspace-id"], "scope_strict": true, "note": "This command has only a numeric DingDrive space target. Its command-scoped --space/--workspace/--workspace-id aliases preserve compatibility published before the workspace/storage-space concept split and pass the value unchanged to --space-id; new commands must not infer that knowledge-base workspace IDs and numeric storage-space IDs are globally interchangeable."},
"drive commit": {"scoped_aliases": {"directory-id": "folder", "parent-directory-id": "folder", "upload-session-id": "upload-id", "size-bytes": "file-size", "name": "file-name", "display-name": "file-name", "filename": "file-name", "upload-name": "file-name"}, "scope_strict": true, "note": "Upload session, file name, file size in bytes, parent folder, and storage space remain separate roles.", "block": ["knowledge-base-id", "wiki-workspace-id", "workspace", "workspace-id"]},
"drive mkdir": {"scoped_aliases": {"directory-id": "folder", "parent-directory-id": "folder", "folder-name": "name", "display-name": "name", "new-name": "name"}, "scope_strict": true, "note": "The name is this exact folder/node display name; search query and local path are different roles.", "block": ["knowledge-base-id", "wiki-workspace-id", "workspace", "workspace-id"]},
"drive upload-info": {"scoped_aliases": {"directory-id": "folder", "parent-directory-id": "folder", "content-type": "mime-type", "size-bytes": "file-size", "name": "file-name", "display-name": "file-name", "filename": "file-name", "upload-name": "file-name"}, "scope_strict": true, "note": "File metadata aliases preserve MIME and byte units; file path and upload session are different stages.", "block": ["knowledge-base-id", "wiki-workspace-id", "workspace", "workspace-id"]},
"drive recycle list": {"block": ["knowledge-base-id", "wiki-workspace-id", "workspace", "workspace-id"], "scope_strict": true, "note": "This command has only a numeric DingDrive space target; knowledge-base workspace spellings are a different value domain."},
"drive search": {"ambiguous": ["end", "from", "start", "time-from", "time-to", "to", "types"], "block": ["offset", "page"], "scope_strict": true, "note": "Created/modified ranges and file/content type arrays are separate roles; generic time/type names are not guessed."},
"drive +search": {"ambiguous": ["end", "from", "start", "time-from", "time-to", "to", "types"], "block": ["offset", "page"], "scope_strict": true, "note": "Created/modified ranges and file/content type arrays are separate roles; generic time/type names are not guessed."},
"drive +star-add": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node", "doc-id": "node", "document-id": "node", "url": "node", "document-url": "node", "id": "node"}, "block": ["dentry-id", "doc", "folder", "folder-id", "name"], "scope_strict": true, "note": "The shortcut calls the same mark_star nodeId interface as drive star add, so its reviewed document/node ID and URL aliases are preserved unchanged. Numeric dentryId and folder/name roles remain protected."},
"drive +star-remove": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node", "doc-id": "node", "document-id": "node", "url": "node", "document-url": "node", "id": "node"}, "block": ["dentry-id", "doc", "folder", "folder-id", "name"], "scope_strict": true, "note": "The shortcut calls the same unmark_star nodeId interface as drive star remove, so its reviewed document/node ID and URL aliases are preserved unchanged. Numeric dentryId and folder/name roles remain protected."},
"drive +stats": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node", "doc-id": "node", "document-id": "node", "url": "node", "document-url": "node", "id": "node"}, "block": ["dentry-id", "doc", "folder", "folder-id", "name"], "scope_strict": true, "note": "The shortcut calls the same get_node_stats nodeId interface as drive stats, so its reviewed ID/URL aliases are preserved unchanged. Numeric dentryId and folder/name roles remain protected."},
"drive +version-download": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node"}, "block": ["dentry-id", "doc", "doc-id", "document-id", "document-url", "file", "file-path", "folder", "folder-id", "id", "name", "url"], "scope_strict": true, "note": "Ordinary-file node, positive historical version number and local output path are three distinct roles; revision and Doc URL spellings must not be guessed."},
"drive +version-get": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node"}, "block": ["dentry-id", "doc", "doc-id", "document-id", "document-url", "folder", "folder-id", "id", "name", "url"], "scope_strict": true, "note": "This command reads one ordinary-file historical version by node ID and positive version number; document revision and URL roles are different."},
"drive +version-history": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node"}, "block": ["dentry-id", "doc", "doc-id", "document-id", "document-url", "folder", "folder-id", "id", "name", "url"], "scope_strict": true, "note": "This command pages versions for one ordinary-file Drive node; document URLs and numeric dentryId are not accepted."},
"drive +version-revert": {"scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node"}, "block": ["dentry-id", "doc", "doc-id", "document-id", "document-url", "folder", "folder-id", "id", "name", "url"], "scope_strict": true, "note": "The high-risk revert targets one ordinary-file Drive node and a positive historical version number. Doc revisions and URLs must stop before confirmation or write dispatch."},
"drive recycle restore": {"bind": {"id": "drive_recycle_item_id"}, "block": ["file-id", "folder-id", "node", "node-id", "space-id", "workspace-id"], "scope_strict": true, "note": "The real --id is a recycle-item ID from recycle list, not a normal Drive node ID."},
"drive star list": {"ambiguous": ["type", "types"], "scope_strict": true, "note": "Content types and resource types are separate arrays; a generic type list cannot choose one.", "scoped_aliases": {"order-field": "order-by", "sort-by": "order-by", "sort-field": "order-by"}},
"drive recent": {"ambiguous": ["type", "types"], "scope_strict": true, "note": "Creator type, operation type, and file type filters are distinct and keep their enum/list forms."},
"drive +recent": {"ambiguous": ["type", "types"], "scope_strict": true, "note": "Creator type, operation type, and file type filters are distinct and keep their enum/list forms."},
"drive +star-list": {"ambiguous": ["type", "types"], "scope_strict": true, "note": "The shortcut exposes only the API contentTypes filter. Generic type spellings may denote file extensions or node/resource types, so the current name-only layer must not guess the value domain."},
"drive +upload": {"ambiguous": ["destination-id", "space", "target-id"], "scoped_aliases": {"dentry-uuid": "node", "file-id": "node", "node-id": "node", "overwrite-node-id": "node", "directory-id": "folder", "parent-directory-id": "folder", "parent-folder-id": "folder", "target-folder-id": "folder", "source-file": "file", "local-file": "file", "file-path": "file", "content-type": "mime-type", "filename": "file-name", "name": "file-name", "display-name": "file-name", "upload-name": "file-name"}, "block": ["dentry-id", "document-url", "knowledge-base-id", "wiki-workspace-id", "workspace", "workspace-id", "output-path"], "scope_strict": true, "note": "Local source path, remote display name, MIME type, parent folder, numeric storage space and optional overwrite node are distinct roles. ID-suffixed file/node spellings denote the overwrite target; the shortcut does not expose a knowledge-base workspace route."}
},
"validation_fixture": {
"cases": [
{"command": "oa +search-forms", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 28},
{"command": "aitable +list-tables", "emitted": "base-id", "expect": "base", "via": "concept:base_id+morph", "occ": 26},
{"command": "mail +find-mail-user", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 18},
{"command": "aitable +field-get", "emitted": "base", "expect": "base-id", "via": "concept:base_id+morph", "occ": 8},
{"command": "aitable +record-query", "emitted": "base", "expect": "base-id", "via": "concept:base_id+morph", "occ": 8},
{"command": "aitable +table-get", "emitted": "base", "expect": "base-id", "via": "concept:base_id+morph", "occ": 8},
{"command": "doc block update", "emitted": "content", "expect": "text", "via": "concept:content_text", "occ": 6},
{"command": "contact +resolve-dept", "emitted": "query", "expect": "name", "via": "concept:search_query+bind", "occ": 4},
{"command": "devdoc article search", "emitted": "limit", "expect": "size", "via": "concept:pagination_size", "occ": 2},
{"command": "devdoc article search", "emitted": "page-size", "expect": "size", "via": "concept:pagination_size", "occ": 2},
{"command": "devdoc article search", "emitted": "current-page", "expect": "page", "via": "concept:page_number", "occ": 2},
{"command": "mail message search", "emitted": "subject", "expect": "query", "via": "override:scoped_strict", "occ": 4},
{"command": "aitable +record-share-url", "emitted": "base", "expect": "base-id", "via": "concept:base_id+morph", "occ": 3},
{"command": "aitable record query", "emitted": "max-results", "expect": "limit", "via": "concept:pagination_size", "occ": 2},
{"command": "calendar event list", "emitted": "date", "expect": "start", "via": "override:scoped(reviewed+payload)", "occ": 2},
{"command": "calendar event list", "emitted": "start-time", "expect": "start", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "min-time", "expect": "start", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "time-min", "expect": "start", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "end-time", "expect": "end", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "time-max", "expect": "end", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "max-results", "expect": "limit", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "next-cursor", "expect": "cursor", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "calendar", "expect": "calendar-id", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "chat message list", "emitted": "max-results", "expect": "limit", "via": "concept:pagination_size", "occ": 2},
{"command": "chat message list-by-sender", "emitted": "time", "expect": "did-you-mean:blocked", "via": "guard:time-format-boundary", "occ": 2},
{"command": "doc +template-search", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 2},
{"command": "doc block insert", "emitted": "content", "expect": "text", "via": "concept:content_text", "occ": 2},
{"command": "drive list", "emitted": "folder-id", "expect": "folder", "via": "concept:folder_id+morph", "occ": 2},
{"command": "mail thread list", "emitted": "max-results", "expect": "limit", "via": "concept:pagination_size", "occ": 2},
{"command": "mail user search", "emitted": "query", "expect": "keyword", "via": "concept:search_query", "occ": 2},
{"command": "oa +list-executed", "emitted": "take", "expect": "limit", "via": "concept:pagination_size", "occ": 2},
{"command": "oa approval search-forms", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 2},
{"command": "report list", "emitted": "from-date", "expect": "start", "via": "concept:time_start", "occ": 2},
{"command": "aitable +base-search", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 1},
{"command": "contact +search-user", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 1},
{"command": "chat group rename", "emitted": "group", "expect": "id", "via": "override:bind(open_conversation_id)", "occ": 31},
{"command": "ding +receiver-status", "emitted": "id", "expect": "ding-id", "via": "override:scoped(ding_id)", "occ": 24},
{"command": "chat group members", "emitted": "group", "expect": "id", "via": "override:bind(open_conversation_id)", "occ": 8},
{"command": "contact +list-sub-depts", "emitted": "dept-id", "expect": "dept", "via": "concept:dept_id+morph", "occ": 4},
{"command": "contact +list-sub-depts", "emitted": "name", "expect": "did-you-mean:blocked", "via": "guard:name-vs-id", "occ": 2},
{"command": "contact +list-sub-depts", "emitted": "query", "expect": "did-you-mean:blocked", "via": "guard:query-vs-id", "occ": 2},
{"command": "contact user profile get", "emitted": "user-id", "expect": "staff-id", "via": "concept:user_id", "occ": 2},
{"command": "contact user profile get", "emitted": "id", "expect": "staff-id", "via": "override:scoped", "occ": 2},
{"command": "contact user profile get", "emitted": "ids", "expect": "staff-id", "via": "override:scoped", "occ": 2},
{"command": "chat message send-by-bot", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list", "occ": 4},
{"command": "chat message send-by-bot", "emitted": "to-user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list", "occ": 1},
{"command": "dev app get", "emitted": "app-id", "expect": "unified-app-id", "via": "concept:app_id", "occ": 5},
{"command": "chat message list-all", "emitted": "from", "expect": "start", "via": "concept:time_start", "occ": 2},
{"command": "chat message list-all", "emitted": "start-time", "expect": "start", "via": "concept:time_start", "occ": 2},
{"command": "chat message search-advanced", "emitted": "group", "expect": "conversation-ids", "via": "native:reviewed-single-to-list", "occ": 4},
{"command": "chat message send", "emitted": "to-user", "expect": "user", "via": "override:scoped(reviewed+payload)", "occ": 4},
{"command": "contact +dept-members", "emitted": "name", "expect": "dept", "via": "override:scoped(reviewed)", "occ": 2},
{"command": "contact +dept-members", "emitted": "query", "expect": "dept", "via": "concept:search_query+bind", "occ": 2},
{"command": "contact dept list-children", "emitted": "parent-id", "expect": "dept", "via": "concept:dept_id", "occ": 2},
{"command": "contact dept list-children", "emitted": "parent", "expect": "dept", "via": "concept:dept_id", "occ": 2},
{"command": "ding message receiver-status", "emitted": "id", "expect": "ding-id", "via": "override:scoped(ding_id)", "occ": 2},
{"command": "ding message receiver-status", "emitted": "open-ding-id", "expect": "ding-id", "via": "concept:ding_id", "occ": 2},
{"command": "chat group members add", "emitted": "group", "expect": "id", "via": "override:bind(open_conversation_id)", "occ": 3},
{"command": "attendance +check-result", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "concept:user_ids+exclude", "occ": 2},
{"command": "attendance check result", "emitted": "user-ids", "expect": "users", "via": "concept:user_ids", "occ": 2},
{"command": "chat group members remove", "emitted": "group", "expect": "id", "via": "override:bind(open_conversation_id)", "occ": 2},
{"command": "chat group set-admin", "emitted": "user-id", "expect": "user", "via": "native:reviewed-compatibility-alias", "occ": 2},
{"command": "ding message send", "emitted": "robot", "expect": "robot-code", "via": "concept:robot_code", "occ": 2},
{"command": "doc +export-get", "emitted": "node", "expect": "did-you-mean:blocked", "via": "override:block", "occ": 2},
{"command": "doc block delete", "emitted": "index", "expect": "did-you-mean:blocked", "via": "override:block", "occ": 2},
{"command": "doc block insert", "emitted": "before-block-id", "expect": "did-you-mean:blocked", "via": "guard:requires-multi-parameter-transform", "occ": 2},
{"command": "drive info", "emitted": "workspace", "expect": "space-id", "via": "override:published-storage-space-compatibility", "occ": 2},
{"command": "mail folder update", "emitted": "folder-id", "expect": "id", "via": "override:bind(folder_id)", "occ": 2},
{"command": "report outbox list", "emitted": "template-type", "expect": "did-you-mean:blocked", "via": "override:block", "occ": 2},
{"command": "chat +group-members", "emitted": "group-name", "expect": "group", "via": "concept:group_name+bind"},
{"command": "chat group get-by-group-id", "emitted": "conversation-id", "expect": "did-you-mean:blocked", "via": "guard:open-conversation-id-vs-group-id"},
{"command": "chat group get-by-group-id", "emitted": "id", "expect": "did-you-mean:blocked", "via": "guard:generic-id-vs-group-id"},
{"command": "chat group rename", "emitted": "conversation-id", "expect": "id", "via": "concept:open_conversation_id+bind"},
{"command": "chat group rename", "emitted": "group-id", "expect": "did-you-mean:blocked", "via": "guard:group-id-vs-open-conversation-id"},
{"command": "chat message send", "emitted": "conversation-id", "expect": "group", "via": "concept:open_conversation_id"},
{"command": "chat message add-emoji", "emitted": "open-conversation-id", "expect": "conversation-id", "via": "override:scoped"},
{"command": "chat message add-emoji", "emitted": "group-id", "expect": "did-you-mean:blocked", "via": "guard:group-id-vs-open-conversation-id"},
{"command": "chat +group-members", "emitted": "conversation-id", "expect": "did-you-mean:blocked", "via": "guard:group-name-vs-open-conversation-id"},
{"command": "chat +send-to-group", "emitted": "group-name", "expect": "group", "via": "concept:group_name+bind"},
{"command": "chat message search-advanced", "emitted": "open-conversation-ids", "expect": "conversation-ids", "via": "concept:open_conversation_ids"},
{"command": "chat message search-advanced", "emitted": "group-ids", "expect": "did-you-mean:blocked", "via": "guard:group-id-list-vs-open-conversation-id-list"},
{"command": "chat message recall", "emitted": "message-id", "expect": "msg-id", "via": "concept:open_message_id"},
{"command": "chat message add-favorite", "emitted": "msg-id", "expect": "open-message-id", "via": "concept:open_message_id"},
{"command": "chat message list-by-ids", "emitted": "message-ids", "expect": "msg-ids", "via": "concept:open_message_ids"},
{"command": "chat message list-by-ids", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat message reply", "emitted": "ref-message-id", "expect": "ref-msg-id", "via": "concept:referenced_open_message_id"},
{"command": "chat message reply", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:message-role"},
{"command": "chat message forward-topic", "emitted": "src-open-message-id", "expect": "src-msg-id", "via": "override:scoped-role"},
{"command": "chat message forward-topic", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:message-role"},
{"command": "chat message combine-forward", "emitted": "src-open-cid", "expect": "src-conversation-id", "via": "override:scoped-role"},
{"command": "chat message forward-topic", "emitted": "dest-open-conversation-id", "expect": "dest-conversation-id", "via": "override:scoped-role"},
{"command": "chat message forward", "emitted": "conversation-id", "expect": "did-you-mean:ambiguous", "via": "guard:source-vs-destination-role"},
{"command": "chat group share-invite", "emitted": "conversation-id", "expect": "did-you-mean:ambiguous", "via": "guard:source-vs-target-role"},
{"command": "chat message send", "emitted": "user-id", "expect": "user", "via": "concept:user_id"},
{"command": "chat message list", "emitted": "user-id", "expect": "user", "via": "concept:user_id"},
{"command": "chat +messages-list-direct", "emitted": "user-id", "expect": "user", "via": "concept:user_id"},
{"command": "attendance +check-result", "emitted": "user-ids", "expect": "users", "via": "concept:user_ids"},
{"command": "contact +list-sub-depts", "emitted": "parent-id", "expect": "dept", "via": "concept:dept_id"},
{"command": "chat +conversation-info", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:user-id-vs-open-dingtalk-id"},
{"command": "chat group members remove", "emitted": "user-ids", "expect": "users", "via": "concept:user_ids"},
{"command": "chat group members remove", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat group create", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +chat-set-admin", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +messages-read-status", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat group members list-by-ids", "emitted": "open-dingtalk-ids", "expect": "users", "via": "concept:open_dingtalk_ids+bind"},
{"command": "chat group members remove-bot", "emitted": "open-bot-id", "expect": "bot-id", "via": "concept:open_bot_id"},
{"command": "chat group members remove-bot", "emitted": "robot-code", "expect": "did-you-mean:blocked", "via": "guard:robot-code-vs-open-bot-id"},
{"command": "chat group members add-bot", "emitted": "robot", "expect": "robot-code", "via": "concept:robot_code"},
{"command": "chat +bot-find", "emitted": "name", "expect": "query", "via": "override:scoped"},
{"command": "chat bot find", "emitted": "name", "expect": "query", "via": "override:scoped"},
{"command": "chat +bot-search", "emitted": "query", "expect": "name", "via": "override:scoped"},
{"command": "chat bot search", "emitted": "query", "expect": "name", "via": "override:scoped"},
{"command": "chat message list-favorites", "emitted": "limit", "expect": "size", "via": "override:scoped"},
{"command": "chat bot search", "emitted": "current-page", "expect": "page", "via": "override:scoped"},
{"command": "chat bot search", "emitted": "cursor", "expect": "did-you-mean:blocked", "via": "guard:page-number-vs-cursor"},
{"command": "chat message list-unread-conversations", "emitted": "limit", "expect": "count", "via": "override:scoped"},
{"command": "chat +messages-list-unread-conversations", "emitted": "size", "expect": "count", "via": "override:scoped"},
{"command": "chat message list", "emitted": "start", "expect": "time", "via": "override:scoped"},
{"command": "chat message list", "emitted": "end", "expect": "did-you-mean:blocked", "via": "guard:single-time-vs-range"},
{"command": "chat message list-all", "emitted": "time", "expect": "did-you-mean:blocked", "via": "guard:time-range-required"},
{"command": "chat message list-by-sender", "emitted": "user-id", "expect": "sender-user-id", "via": "override:scoped-role"},
{"command": "chat message list-by-sender", "emitted": "open-dingtalk-id", "expect": "sender-open-dingtalk-id", "via": "override:scoped-role"},
{"command": "chat category create-smart", "emitted": "title", "expect": "name", "via": "override:scoped"},
{"command": "chat category create", "emitted": "name", "expect": "title", "via": "override:scoped"},
{"command": "chat message send", "emitted": "file", "expect": "file-path", "via": "override:scoped"},
{"command": "chat category add-conv", "emitted": "category-id", "expect": "did-you-mean:blocked", "via": "override:block-cardinality"},
{"command": "chat category rename", "emitted": "category-ids", "expect": "did-you-mean:blocked", "via": "override:block-cardinality"},
{"command": "chat group-role set-user", "emitted": "role-id", "expect": "did-you-mean:blocked", "via": "override:block-cardinality"},
{"command": "chat group-role update", "emitted": "role-ids", "expect": "did-you-mean:blocked", "via": "override:block-cardinality"},
{"command": "chat message send-by-webhook", "emitted": "at-user-ids", "expect": "at-users", "via": "override:scoped-role"},
{"command": "chat message send-by-bot", "emitted": "at-users", "expect": "at-user-ids", "via": "override:scoped-role"},
{"command": "chat message send-by-bot", "emitted": "at-ids", "expect": "did-you-mean:ambiguous", "via": "guard:user-id-vs-open-dingtalk-id"},
{"command": "chat +bot-search", "emitted": "current-page", "expect": "page", "via": "override:scoped"},
{"command": "chat +category-create", "emitted": "name", "expect": "title", "via": "override:scoped"},
{"command": "chat +category-rename", "emitted": "name", "expect": "title", "via": "override:scoped"},
{"command": "chat +messages-list-direct", "emitted": "start", "expect": "time", "via": "override:scoped"},
{"command": "chat +messages-list-unread-conversations", "emitted": "limit", "expect": "count", "via": "override:scoped"},
{"command": "chat +messages-send-by-webhook", "emitted": "at-user-ids", "expect": "at-users", "via": "override:scoped-role"},
{"command": "chat +unread-chats", "emitted": "limit", "expect": "count", "via": "override:scoped"},
{"command": "chat +unread-chats", "emitted": "size", "expect": "count", "via": "override:scoped"},
{"command": "chat category rename", "emitted": "name", "expect": "title", "via": "override:scoped"},
{"command": "chat message list-unread-conversations", "emitted": "size", "expect": "count", "via": "override:scoped"},
{"command": "doc +comment-create", "emitted": "node-id", "expect": "node", "via": "concept:doc_node_id"},
{"command": "doc +doc-append", "emitted": "node", "expect": "doc", "via": "concept:doc_node_id"},
{"command": "doc +find-doc", "emitted": "keyword", "expect": "query", "via": "concept:search_query"},
{"command": "doc +search", "emitted": "q", "expect": "query", "via": "concept:search_query"},
{"command": "doc +comment-list", "emitted": "max-results", "expect": "limit", "via": "concept:pagination_size"},
{"command": "doc +list", "emitted": "page-token", "expect": "cursor", "via": "concept:page_cursor"},
{"command": "doc +copy", "emitted": "workspace-id", "expect": "workspace", "via": "concept:workspace_id"},
{"command": "doc +copy", "emitted": "parent-folder-id", "expect": "folder", "via": "override:scoped-doc-folder"},
{"command": "doc +copy", "emitted": "parent-id", "expect": "did-you-mean:blocked", "via": "guard:doc-folder-value-domain"},
{"command": "doc +comment-reply", "emitted": "comment-id", "expect": "comment-key", "via": "concept:doc_comment_key"},
{"command": "doc comment reply", "emitted": "mentioned-open-conversation-ids", "expect": "mentioned-open-conversation-id", "via": "override:scoped-role-list"},
{"command": "doc comment reply", "emitted": "group-id", "expect": "did-you-mean:blocked", "via": "guard:numeric-group-id-vs-open-conversation-id"},
{"command": "doc +comment-create", "emitted": "mentioned-open-conversation-id", "expect": "did-you-mean:blocked", "via": "guard:shortcut-missing-capability"},
{"command": "doc block insert", "emitted": "parent-block-id", "expect": "parent-block", "via": "override:scoped-block-role"},
{"command": "doc block insert", "emitted": "block-id", "expect": "did-you-mean:ambiguous", "via": "guard:parent-vs-reference-block-role"},
{"command": "doc media insert", "emitted": "before-block-id", "expect": "did-you-mean:blocked", "via": "guard:requires-ref-block-plus-where"},
{"command": "doc read", "emitted": "block-id", "expect": "did-you-mean:ambiguous", "via": "guard:requires-scope-and-boundary-role"},
{"command": "doc export get", "emitted": "node", "expect": "did-you-mean:blocked", "via": "guard:document-node-vs-export-job"},
{"command": "doc import get", "emitted": "job-id", "expect": "did-you-mean:blocked", "via": "guard:export-job-vs-import-task"},
{"command": "doc +version-revert", "emitted": "version-number", "expect": "version", "via": "concept:doc_version_number"},
{"command": "doc +version-revert", "emitted": "revision", "expect": "did-you-mean:blocked", "via": "concept:doc_version_number+exclude"},
{"command": "doc update", "emitted": "version", "expect": "did-you-mean:blocked", "via": "guard:historical-version-vs-edit-revision"},
{"command": "doc +share-doc", "emitted": "node-id", "expect": "did-you-mean:blocked", "via": "guard:node-id-needs-url-conversion"},
{"command": "doc +comment-create", "emitted": "body", "expect": "content", "via": "concept:content_text"},
{"command": "doc +doc-append", "emitted": "content", "expect": "text", "via": "concept:content_text"},
{"command": "doc +export-submit", "emitted": "doc-id", "expect": "node", "via": "concept:doc_node_id"},
{"command": "doc +move", "emitted": "parent-folder-id", "expect": "folder", "via": "override:scoped-doc-folder"},
{"command": "doc +template-list", "emitted": "next-token", "expect": "cursor", "via": "concept:page_cursor"},
{"command": "doc +version-list", "emitted": "page-size", "expect": "limit", "via": "concept:pagination_size"},
{"command": "doc +version-save", "emitted": "file-id", "expect": "node", "via": "concept:doc_node_id"},
{"command": "doc comment create", "emitted": "text", "expect": "content", "via": "concept:content_text"},
{"command": "doc comment create-inline", "emitted": "body", "expect": "content", "via": "concept:content_text"},
{"command": "doc comment delete", "emitted": "comment-id", "expect": "comment-key", "via": "concept:doc_comment_key"},
{"command": "doc comment update", "emitted": "comment-id", "expect": "comment-key", "via": "concept:doc_comment_key"},
{"command": "doc version revert", "emitted": "version-no", "expect": "version", "via": "concept:doc_version_number"},
{"command": "chat +chat-messages", "emitted": "chat", "expect": "group", "via": "override:scoped-eval"},
{"command": "chat +search-msg", "emitted": "chat", "expect": "group", "via": "override:scoped-eval"},
{"command": "chat +chat-update", "emitted": "chat-id", "expect": "group", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-update", "emitted": "conversation-id", "expect": "group", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-update", "emitted": "open-conversation-id", "expect": "group", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-update", "emitted": "id", "expect": "did-you-mean:blocked", "via": "guard:generic-id-value-domain"},
{"command": "chat +chat-update", "emitted": "title", "expect": "name", "via": "override:scoped-group-title"},
{"command": "chat +chat-update", "emitted": "new-title", "expect": "name", "via": "override:scoped-group-title"},
{"command": "chat +flag-list", "emitted": "limit", "expect": "page-size", "via": "override:scoped-page-bound"},
{"command": "chat +flag-list", "emitted": "max", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +flag-list", "emitted": "max-results", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +flag-list", "emitted": "max-size", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +chat-members-list", "emitted": "chat-id", "expect": "conversation-id", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-members-list", "emitted": "id", "expect": "conversation-id", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-members-list", "emitted": "query", "expect": "did-you-mean:blocked", "via": "guard:unsupported-member-filter"},
{"command": "chat +conversation-set-top", "emitted": "open-conversation-id", "expect": "conversation-id", "via": "override:scoped-explicit-cid"},
{"command": "chat +conversation-set-top", "emitted": "chat-ids", "expect": "conversation-ids", "via": "override:scoped-explicit-cid-list"},
{"command": "chat +conversation-set-top", "emitted": "groups", "expect": "did-you-mean:blocked", "via": "guard:group-name-or-list-ambiguity"},
{"command": "chat +conversation-set-top", "emitted": "top", "expect": "did-you-mean:blocked", "via": "guard:inverse-boolean-semantics"},
{"command": "chat +chat-members-get", "emitted": "conversation-id", "expect": "id", "via": "concept:open_conversation_id+bind"},
{"command": "chat +chat-members-get", "emitted": "open-dingtalk-ids", "expect": "users", "via": "concept:open_dingtalk_ids+bind"},
{"command": "chat +chat-members-get", "emitted": "group", "expect": "did-you-mean:blocked", "via": "guard:group-name-vs-open-conversation-id"},
{"command": "chat +chat-members-get", "emitted": "chat", "expect": "id", "via": "concept:open_conversation_id+bind"},
{"command": "chat +chat-get-by-id", "emitted": "group", "expect": "did-you-mean:blocked", "via": "guard:open-conversation-id-vs-numeric-group-id"},
{"command": "chat +messages-list", "emitted": "start", "expect": "time", "via": "override:scoped-time-boundary"},
{"command": "chat +messages-list", "emitted": "count", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +messages-list", "emitted": "max-results", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +messages-list", "emitted": "page-all", "expect": "did-you-mean:blocked", "via": "guard:requires-pagination-loop"},
{"command": "chat +messages-reply", "emitted": "msg-id", "expect": "ref-msg-id", "via": "override:scoped-reference-message"},
{"command": "chat +messages-reply", "emitted": "group", "expect": "did-you-mean:blocked", "via": "guard:group-name-vs-open-conversation-id"},
{"command": "chat +messages-reply", "emitted": "chat", "expect": "conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +flag-cancel", "emitted": "group", "expect": "conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +flag-cancel", "emitted": "chat", "expect": "conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +flag-create", "emitted": "group", "expect": "conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +chat-add-bot", "emitted": "conversation-id", "expect": "id", "via": "concept:open_conversation_id+bind"},
{"command": "chat +chat-add-bot", "emitted": "robot", "expect": "robot-code", "via": "concept:robot_code"},
{"command": "chat +chat-audit-join", "emitted": "applicant-user-id", "expect": "applicant", "via": "override:scoped-user-role"},
{"command": "chat +chat-audit-join", "emitted": "user-id", "expect": "did-you-mean:ambiguous", "via": "guard:applicant-vs-inviter-role"},
{"command": "chat +chat-create", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +chat-mute-member", "emitted": "user-ids", "expect": "users", "via": "override:scoped-mixed-id-list"},
{"command": "chat +chat-mute-member", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +chat-remove-bot", "emitted": "open-bot-id", "expect": "bot-id", "via": "concept:open_bot_id"},
{"command": "chat +chat-role-remove", "emitted": "role-ids", "expect": "did-you-mean:blocked", "via": "guard:list-vs-single"},
{"command": "chat +chat-role-remove-user", "emitted": "open-dingtalk-id", "expect": "user", "via": "override:scoped-mixed-id"},
{"command": "chat +chat-transfer-owner", "emitted": "user-id", "expect": "new-owner", "via": "override:scoped-owner-role"},
{"command": "chat +feed-group-query-item", "emitted": "chat-ids", "expect": "conversation-ids", "via": "override:scoped-explicit-cid-list"},
{"command": "chat +feed-group-query-item", "emitted": "conversation-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +messages-batch-recall-by-bot", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:process-query-key-vs-open-message-id"},
{"command": "chat +messages-combine-forward", "emitted": "src-open-cid", "expect": "src-conversation-id", "via": "override:scoped-source-role"},
{"command": "chat +messages-combine-forward", "emitted": "conversation-id", "expect": "did-you-mean:ambiguous", "via": "guard:source-vs-destination-role"},
{"command": "chat +messages-forward", "emitted": "source-message-id", "expect": "msg-id", "via": "override:scoped-source-role"},
{"command": "chat +messages-forward", "emitted": "conversation-id", "expect": "did-you-mean:ambiguous", "via": "guard:source-vs-destination-role"},
{"command": "chat +messages-forward-topic", "emitted": "src-open-message-id", "expect": "src-msg-id", "via": "override:scoped-source-role"},
{"command": "chat +messages-forward-topic", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:message-source-role"},
{"command": "chat +messages-recall-by-bot", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:process-query-key-vs-open-message-id"},
{"command": "chat +messages-resource-download", "emitted": "conversation-id", "expect": "open-conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +messages-resource-download", "emitted": "download-dir", "expect": "did-you-mean:blocked", "via": "guard:output-file-or-directory-contract"},
{"command": "chat +messages-set-pin", "emitted": "conversation-id", "expect": "open-conversation-id", "via": "concept:open_conversation_id"},
{"command": "doc +create", "emitted": "content-format", "expect": "doc-format", "via": "concept:doc_content_format"},
{"command": "doc +create", "emitted": "content-file", "expect": "did-you-mean:blocked", "via": "guard:requires-file-read-transform"},
{"command": "doc +inspect", "emitted": "include-versions", "expect": "include-history", "via": "override:scoped-section"},
{"command": "doc +inspect", "emitted": "include", "expect": "did-you-mean:blocked", "via": "guard:requires-value-dependent-flag-expansion", "occ": 1},
{"command": "doc +inspect", "emitted": "include-info", "expect": "did-you-mean:blocked", "via": "guard:base-info-always-returned", "occ": 1},
{"command": "doc +update", "emitted": "mode", "expect": "command", "via": "override:scoped-operation", "occ": 5},
{"command": "doc +update", "emitted": "revision", "expect": "expected-revision", "via": "concept:doc_edit_revision"},
{"command": "doc +update", "emitted": "version", "expect": "did-you-mean:blocked", "via": "guard:historical-version-vs-edit-revision"},
{"command": "doc +fetch", "emitted": "start-block", "expect": "start-block-id", "via": "override:scoped-boundary-role"},
{"command": "doc +fetch", "emitted": "block-id", "expect": "did-you-mean:ambiguous", "via": "guard:start-vs-end-boundary-role"},
{"command": "doc +access-grant", "emitted": "doc-id", "expect": "node", "via": "concept:doc_node_id"},
{"command": "doc +history-revert", "emitted": "version-number", "expect": "version", "via": "concept:doc_version_number"},
{"command": "doc +create-from-template", "emitted": "keyword", "expect": "query", "via": "concept:search_query"},
{"command": "doc +create-from-template", "emitted": "workspace-id", "expect": "workspace", "via": "concept:workspace_id"},
{"command": "doc +create-from-template", "emitted": "parent-folder-id", "expect": "folder", "via": "override:scoped-doc-folder"},
{"command": "doc +media-download", "emitted": "file-id", "expect": "did-you-mean:ambiguous", "via": "guard:document-node-vs-attachment-resource-role"},
{"command": "doc +resource-update", "emitted": "url", "expect": "did-you-mean:ambiguous", "via": "guard:document-url-vs-image-url-role"},
{"command": "doc +share", "emitted": "node-id", "expect": "did-you-mean:blocked", "via": "guard:node-id-needs-url-conversion"},
{"command": "doc +copy", "emitted": "dentry-uuid", "expect": "node", "via": "concept:doc_node_id"},
{"command": "doc info", "emitted": "dentry-id", "expect": "did-you-mean:blocked", "via": "guard:dentry-id-vs-dentry-uuid"},
{"command": "doc create", "emitted": "knowledge-base-id", "expect": "workspace", "via": "concept:workspace_id"},
{"command": "doc create", "emitted": "space-id", "expect": "workspace", "via": "override:published-workspace-compatibility"},
{"command": "drive list", "emitted": "knowledge-base-id", "expect": "workspace", "via": "concept:workspace_id"},
{"command": "drive list", "emitted": "order-field", "expect": "order-by", "via": "override:scoped-sort-field"},
{"command": "drive info", "emitted": "dentry-uuid", "expect": "node", "via": "override:scoped-node-id"},
{"command": "drive info", "emitted": "dentry-id", "expect": "did-you-mean:blocked", "via": "guard:dentry-id-vs-dentry-uuid"},
{"command": "drive copy", "emitted": "target-folder-id", "expect": "folder", "via": "override:scoped-destination-folder"},
{"command": "drive copy", "emitted": "target-id", "expect": "did-you-mean:ambiguous", "via": "guard:destination-role-required"},
{"command": "drive search", "emitted": "created-after", "expect": "created-from", "via": "concept:created_time_start"},
{"command": "drive search", "emitted": "created-before", "expect": "created-to", "via": "concept:created_time_end"},
{"command": "drive search", "emitted": "modified-after", "expect": "modified-from", "via": "concept:drive_modified_time_start"},
{"command": "drive search", "emitted": "modified-before", "expect": "modified-to", "via": "concept:drive_modified_time_end"},
{"command": "drive search", "emitted": "creator-user-ids", "expect": "creator-uids", "via": "concept:creator_user_ids"},
{"command": "drive permission add", "emitted": "permission-role", "expect": "role", "via": "concept:document_permission_role"},
{"command": "drive permission list", "emitted": "role", "expect": "did-you-mean:blocked", "via": "guard:permission-role-vs-filter-role"},
{"command": "drive upload", "emitted": "source-file", "expect": "file", "via": "override:scoped-local-file"},
{"command": "doc +search", "emitted": "created-after", "expect": "created-from", "via": "concept:created_time_start"},
{"command": "doc +search", "emitted": "create-time-start", "expect": "created-from", "via": "concept:created_time_start", "occ": 1},
{"command": "doc +search", "emitted": "create-time-end", "expect": "created-to", "via": "concept:created_time_end", "occ": 1},
{"command": "doc +search", "emitted": "creator-user-ids", "expect": "creator-uids", "via": "concept:creator_user_ids"},
{"command": "doc +access-grant", "emitted": "permission-role", "expect": "role", "via": "concept:document_permission_role"},
{"command": "doc +export", "emitted": "output-path", "expect": "output", "via": "concept:local_output_path"},
{"command": "drive download", "emitted": "destination-path", "expect": "output", "via": "concept:local_output_path"},
{"command": "drive download", "emitted": "version-number", "expect": "version", "via": "concept:doc_version_number"},
{"command": "drive recycle restore", "emitted": "recycle-item-id", "expect": "id", "via": "concept:drive_recycle_item_id"},
{"command": "drive upload-info", "emitted": "size-bytes", "expect": "file-size", "via": "concept:drive_file_size_bytes"},
{"command": "drive +info", "emitted": "drive-space-id", "expect": "space-id", "via": "concept:drive_storage_space_id"},
{"command": "drive commit", "emitted": "storage-space-id", "expect": "space-id", "via": "concept:drive_storage_space_id"},
{"command": "drive download", "emitted": "dingdrive-space-id", "expect": "space-id", "via": "concept:drive_storage_space_id"},
{"command": "drive info", "emitted": "drive-space-id", "expect": "space-id", "via": "concept:drive_storage_space_id"},
{"command": "drive list", "emitted": "storage-space-id", "expect": "space-id", "via": "concept:drive_storage_space_id"},
{"command": "drive list", "emitted": "sort-direction", "expect": "order", "via": "concept:drive_sort_direction"},
{"command": "drive mkdir", "emitted": "dingdrive-space-id", "expect": "space-id", "via": "concept:drive_storage_space_id"},
{"command": "drive recycle list", "emitted": "drive-space-id", "expect": "space-id", "via": "concept:drive_storage_space_id"},
{"command": "drive upload", "emitted": "storage-space-id", "expect": "space-id", "via": "concept:drive_storage_space_id"},
{"command": "drive upload-info", "emitted": "dingdrive-space-id", "expect": "space-id", "via": "concept:drive_storage_space_id"},
{"command": "doc +access-grant", "emitted": "user", "expect": "did-you-mean:ambiguous", "via": "guard:collaborator-name-vs-id-value-domain", "occ": 6},
{"command": "doc +access-grant", "emitted": "target-user", "expect": "did-you-mean:ambiguous", "via": "guard:collaborator-name-vs-id-value-domain", "occ": 1},
{"command": "doc +access-grant", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:user-id-cannot-feed-name-resolver", "occ": 1},
{"command": "doc +access-grant", "emitted": "user-ids", "expect": "did-you-mean:blocked", "via": "guard:user-id-cannot-feed-name-resolver", "occ": 1},
{"command": "doc +access-grant", "emitted": "target-user-id", "expect": "did-you-mean:blocked", "via": "guard:user-id-cannot-feed-name-resolver"},
{"command": "doc +access-grant", "emitted": "target-user-ids", "expect": "did-you-mean:blocked", "via": "guard:user-id-cannot-feed-name-resolver"},
{"command": "doc +fetch", "emitted": "content-format", "expect": "did-you-mean:blocked", "via": "guard:content-format-vs-detail-contract", "occ": 3},
{"command": "doc +fetch", "emitted": "doc-format", "expect": "did-you-mean:blocked", "via": "guard:content-format-vs-detail-contract", "occ": 1},
{"command": "doc +fetch", "emitted": "range", "expect": "did-you-mean:blocked", "via": "guard:composite-range-needs-block-ids", "occ": 1},
{"command": "doc +inspect", "emitted": "include-access", "expect": "include-permissions", "via": "override:scoped-permission-section", "occ": 3},
{"command": "doc +inspect", "emitted": "include-member", "expect": "include-permissions", "via": "override:scoped-permission-section", "occ": 1},
{"command": "doc +inspect", "emitted": "include-members", "expect": "include-permissions", "via": "override:scoped-permission-section", "occ": 2},
{"command": "doc +inspect", "emitted": "include-meta", "expect": "did-you-mean:blocked", "via": "guard:base-metadata-already-returned", "occ": 2},
{"command": "doc +inspect", "emitted": "include-metadata", "expect": "did-you-mean:blocked", "via": "guard:base-metadata-already-returned", "occ": 1},
{"command": "doc +inspect", "emitted": "include-blocks", "expect": "did-you-mean:blocked", "via": "guard:content-read-belongs-to-fetch", "occ": 1},
{"command": "doc +inspect", "emitted": "include-content", "expect": "did-you-mean:blocked", "via": "guard:content-read-belongs-to-fetch", "occ": 1},
{"command": "doc +inspect", "emitted": "role", "expect": "did-you-mean:blocked", "via": "guard:permission-role-vs-document-node", "occ": 1},
{"command": "doc +copy", "emitted": "name", "expect": "did-you-mean:blocked", "via": "guard:fuzzy-name-vs-node-role", "occ": 2},
{"command": "doc +export", "emitted": "wait", "expect": "did-you-mean:blocked", "via": "guard:workflow-wait-vs-max-polls", "occ": 1},
{"command": "doc +media-insert", "emitted": "after-block-id", "expect": "did-you-mean:blocked", "via": "guard:requires-ref-block-and-where-expansion", "occ": 1},
{"command": "doc +media-insert", "emitted": "before-block-id", "expect": "did-you-mean:blocked", "via": "guard:requires-ref-block-and-where-expansion"},
{"command": "doc +update", "emitted": "content-file", "expect": "did-you-mean:blocked", "via": "guard:requires-file-read-transform", "occ": 1},
{"command": "doc +update", "emitted": "element", "expect": "did-you-mean:ambiguous", "via": "guard:content-vs-block-vs-reference-role", "occ": 1},
{"command": "doc update", "emitted": "stdin", "expect": "did-you-mean:blocked", "via": "guard:stdin-requires-content-dash-transform", "occ": 1},
{"command": "doc +version-save", "emitted": "title", "expect": "did-you-mean:blocked", "via": "guard:unsupported-version-metadata", "occ": 1},
{"command": "doc +version-save", "emitted": "message", "expect": "did-you-mean:blocked", "via": "guard:unsupported-version-metadata", "occ": 1},
{"command": "drive +search", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 1},
{"command": "contact +search-user", "emitted": "name", "expect": "did-you-mean:blocked", "via": "guard:person-name-vs-search-query", "occ": 1},
{"command": "drive copy", "emitted": "name", "expect": "did-you-mean:blocked", "via": "guard:fuzzy-name-vs-node-role"},
{"command": "drive +copy", "emitted": "name", "expect": "did-you-mean:blocked", "via": "guard:fuzzy-name-vs-node-role"},
{"command": "drive move", "emitted": "name", "expect": "did-you-mean:blocked", "via": "guard:fuzzy-name-vs-node-role"},
{"command": "drive +move", "emitted": "name", "expect": "did-you-mean:blocked", "via": "guard:fuzzy-name-vs-node-role"},
{"command": "drive shortcut", "emitted": "name", "expect": "did-you-mean:blocked", "via": "guard:fuzzy-name-vs-node-role"},
{"command": "drive +cover", "emitted": "dentry-uuid", "expect": "node", "via": "override:scoped-drive-node"},
{"command": "drive +cover", "emitted": "name", "expect": "did-you-mean:blocked", "via": "guard:fuzzy-name-vs-drive-node"},
{"command": "drive +create-folder", "emitted": "folder-name", "expect": "name", "via": "override:scoped-folder-name"},
{"command": "drive +create-folder", "emitted": "storage-space-id", "expect": "space-id", "via": "concept:drive_storage_space_id"},
{"command": "drive +create-shortcut", "emitted": "source-file-id", "expect": "node", "via": "override:scoped-source-node"},
{"command": "drive +create-shortcut", "emitted": "target-folder-id", "expect": "folder", "via": "override:scoped-target-folder"},
{"command": "drive +create-shortcut", "emitted": "target-workspace-id", "expect": "workspace", "via": "override:scoped-target-workspace"},
{"command": "drive +create-shortcut", "emitted": "name", "expect": "did-you-mean:blocked", "via": "guard:fuzzy-name-vs-drive-node"},
{"command": "drive +delete", "emitted": "file-id", "expect": "node", "via": "override:scoped-drive-node"},
{"command": "drive +delete", "emitted": "dentry-id", "expect": "did-you-mean:blocked", "via": "guard:dentry-id-vs-node-id"},
{"command": "drive +download", "emitted": "dentry-uuid", "expect": "node", "via": "override:scoped-drive-node"},
{"command": "drive +download", "emitted": "destination-path", "expect": "output", "via": "concept:local_output_path"},
{"command": "drive +inspect", "emitted": "include-statistics", "expect": "include-stats", "via": "override:scoped-inspect-section"},
{"command": "drive +inspect", "emitted": "include-history", "expect": "did-you-mean:blocked", "via": "guard:doc-inspect-section"},
{"command": "drive +list", "emitted": "folder-id", "expect": "folder", "via": "concept:folder_id"},
{"command": "drive +list", "emitted": "page-size", "expect": "limit", "via": "concept:pagination_size"},
{"command": "drive +list", "emitted": "next-token", "expect": "cursor", "via": "concept:page_cursor"},
{"command": "drive +list", "emitted": "sort-direction", "expect": "order", "via": "concept:drive_sort_direction"},
{"command": "drive +list", "emitted": "sort-by", "expect": "order-by", "via": "override:scoped-sort-field"},
{"command": "drive +publish-get", "emitted": "dentry-uuid", "expect": "node", "via": "override:scoped-drive-node"},
{"command": "drive +publish-unset", "emitted": "file-id", "expect": "node", "via": "override:scoped-drive-node"},
{"command": "drive +recycle-list", "emitted": "storage-space-id", "expect": "space-id", "via": "concept:drive_storage_space_id"},
{"command": "drive +recycle-list", "emitted": "page-token", "expect": "cursor", "via": "concept:page_cursor"},
{"command": "drive +recycle-restore", "emitted": "recycle-item-id", "expect": "id", "via": "override:bind(drive_recycle_item_id)"},
{"command": "drive +recycle-restore", "emitted": "node-id", "expect": "did-you-mean:blocked", "via": "guard:recycle-item-vs-node-id"},
{"command": "drive +rename", "emitted": "file-id", "expect": "node", "via": "override:scoped-drive-node"},
{"command": "drive +rename", "emitted": "new-name", "expect": "name", "via": "override:scoped-display-name"},
{"command": "drive +star-add", "emitted": "dentry-uuid", "expect": "node", "via": "override:scoped-drive-node"},
{"command": "drive +star-list", "emitted": "max-results", "expect": "limit", "via": "concept:pagination_size"},
{"command": "drive +star-list", "emitted": "types", "expect": "did-you-mean:ambiguous", "via": "guard:content-type-value-domain"},
{"command": "drive +star-remove", "emitted": "file-id", "expect": "node", "via": "override:scoped-drive-node"},
{"command": "drive +stats", "emitted": "dentry-uuid", "expect": "node", "via": "override:scoped-drive-node"},
{"command": "drive +upload", "emitted": "source-file", "expect": "file", "via": "override:scoped-local-file"},
{"command": "drive +upload", "emitted": "name", "expect": "file-name", "via": "override:scoped-remote-name"},
{"command": "drive +upload", "emitted": "overwrite-node-id", "expect": "node", "via": "override:scoped-overwrite-node"},
{"command": "drive +upload", "emitted": "workspace-id", "expect": "did-you-mean:blocked", "via": "guard:unsupported-workspace-route"},
{"command": "drive +version-download", "emitted": "version-number", "expect": "version", "via": "concept:doc_version_number"},
{"command": "drive +version-download", "emitted": "save-path", "expect": "output", "via": "concept:local_output_path"},
{"command": "drive +version-get", "emitted": "version-no", "expect": "version", "via": "concept:doc_version_number"},
{"command": "drive +version-history", "emitted": "next-cursor", "expect": "cursor", "via": "concept:page_cursor"},
{"command": "drive +version-history", "emitted": "page-size", "expect": "limit", "via": "concept:pagination_size"},
{"command": "drive +version-revert", "emitted": "version-number", "expect": "version", "via": "concept:doc_version_number"},
{"command": "drive +version-revert", "emitted": "revision", "expect": "did-you-mean:blocked", "via": "guard:document-revision-vs-file-version"},
{"command": "drive +cover", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +create-shortcut", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +delete", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +download", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +inspect", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +publish-get", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +publish-unset", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +rename", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +star-add", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +star-remove", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +stats", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +upload", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +version-download", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +version-get", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +version-history", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +version-revert", "emitted": "node-id", "expect": "node", "via": "override:node-id-parity"},
{"command": "drive +cover", "emitted": "url", "expect": "node", "via": "override:existing-command-parity"},
{"command": "drive +create-shortcut", "emitted": "document-id", "expect": "node", "via": "override:existing-command-parity"},
{"command": "drive +delete", "emitted": "folder-id", "expect": "node", "via": "override:single-folder-node-role"},
{"command": "drive +inspect", "emitted": "document-id", "expect": "node", "via": "override:document-node-id"},
{"command": "drive +inspect", "emitted": "folder-id", "expect": "node", "via": "override:single-folder-node-role"},
{"command": "drive +publish-get", "emitted": "url", "expect": "node", "via": "override:existing-command-parity"},
{"command": "drive +publish-unset", "emitted": "document-url", "expect": "node", "via": "override:existing-command-parity"},
{"command": "drive +rename", "emitted": "document-id", "expect": "node", "via": "override:existing-command-parity"},
{"command": "drive +rename", "emitted": "folder-id", "expect": "node", "via": "override:single-folder-node-role"},
{"command": "drive +star-add", "emitted": "url", "expect": "node", "via": "override:existing-command-parity"},
{"command": "drive +star-remove", "emitted": "doc-id", "expect": "node", "via": "override:existing-command-parity"},
{"command": "drive +stats", "emitted": "document-url", "expect": "node", "via": "override:existing-command-parity"},
{"command": "drive +upload", "emitted": "file-id", "expect": "node", "via": "override:overwrite-node-id-role"}
]
}
}
@@ -0,0 +1,188 @@
# Event 产品 CLI 参数幻觉分析
## 结论摘要
本分析以线上 `origin/main` 冻结提交
`aa4ae9a90323aa97e5cebdb5045b129f0f14e0df` 为唯一基线,使用该提交重新构建的
`dws`、运行时 Schema、Cobra Help、Event Runtime、dingtalk-event Skill 及正式
`internal/cli/param_concepts.json`。未使用固定 Catalog、历史 badcase、用户 Shortcut
或已安装插件。当前工作区后续分支推进没有改变该冻结基线,也未被本分析改写。
Event 产品有 6 个 Agent 可见且可执行叶:`+listen-im`、`consume`、`list`、`schema`、
`status`、`stop`。正式别名表对 Event 完全没有 concept、override 或 fixture。主要风险
不是简单拼写,而是把四层接口混在一起:高频 IM 意图 facade、底层 EventKey 位置参数、
bus 本地投递过滤、订阅生命周期控制。典型错误包括把 `--event-key` 当 flag、把
`--events`/`--event-types` 当成同一种选择器、把姓名直接传给底层 consume、混用
userId/openDingTalkId/openConversationId、混淆 query/regex/Filter DSL、把运行时长当
订阅 TTL,以及在破坏性 stop 上把 subscribe_id 写成未知 flag。
候选已通过真实生成器、PreParse、5 组 alias/canonical 逐字节输出比较、9 组
block/ambiguous、非目标结构恒等、`internal/cli`、`internal/pipeline`、generated drift
和 Schema Catalog 政策。完整 `internal/app` 的唯一产品相关失败是 6 个 Event 命令均
缺 complete-command E2E 模板;正式状态为“规则与运行链路已验证,补齐 6 个模板后方可
落地”。
## 参数问题
### 1. `+listen-im` 的 kind、events 与 target 是一个受约束的意图编译层
`event +listen-im` 不是底层 EventKey 的另一种拼写。它用:
- `--kind=at-me|sender|group|all-direct|all-group` 选择监听范围;
- `--events=message,reaction,read,recall` 选择高层事件种类;
- `--user`、`--open-dingtalk-id`、`--user-query`、`--chat-id`、`--chat-query` 中最多一个
选择目标。
`sender` 和 `group` 要求相应目标;`at-me/all-direct/all-group` 不接受目标且只支持
message;`--query` 只适用于纯 message。候选允许 `listen-kind/intent-kind`、
`event-kinds/event-types` 等精确同义写法,并把无角色 `target/id/name` 标为 ambiguous;
raw EventKey、订阅控制、Filter DSL 和输出路由在 facade 上全部 block。
### 2. userId、openDingTalkId、openConversationId 与自然名称不能互换
- `--user` 是一个 userId;
- `--open-dingtalk-id` 是一个 openDingTalkId;
- `+listen-im --chat-id` 与 `consume --group` 都是 openConversationId;
- `--user-query`/`--chat-query` 是 facade 内部唯一解析的自然姓名/群名。
候选扩展既有 `user_id`、`open_conversation_id`,新增单值
`open_dingtalk_id` concept,使同值域拼写在两个监听入口内归一。底层 `consume` 不做
自然名称解析,因此 `user-query/sender-name/chat-query/group-name` 明确 block;复数 ID
也不会自动缩成单值。
### 3. EventKey 是位置参数,`events` 和 `event-types` 都不是替代品
`event consume [event_key...]` 接受一个或多个公开 EventKey 位置参数;
`event schema <event_key>` 也要求位置参数。`consume --event-types` 只是 bus 到本地
consumer 的事件类型投递过滤,省略时按 EventKey 过滤;`+listen-im --events` 则是 facade
的四种高层事件种类。
中央参数表不能把 flag 重写成位置参数。候选因此在 `consume`、`schema` 上 block
`--event-key/--event-keys/--event`,在 facade 上 block raw EventKey;错误会在派发前提示
使用真实位置语法。没有把 `event-types`、`events` 和 EventKey 合并成一个 concept。
### 4. query、filter 与 filter-json 是三种不同过滤层
- `--query` 是逗号分隔的消息正文关键词,只适用于兼容的接收消息 EventKey;
- `--filter` 是客户端事件类型正则,下推到 bus;
- `--filter-json` 是个人事件订阅 Filter DSL JSON。
候选允许 message-query/message-keywords、event-type-regex/filter-regex、
filter-dsl-json/rule-json 的精确角色别名。`filter-file`、`filter-object`、`query-json`、
无角色 `where` 等被 block 或标记 ambiguous。多事件对这些参数还有 Runtime 约束,候选
不尝试按值推断或绕过。
### 5. duration、max-events、TTL 与全局 timeout 的单位和生命周期不同
`--duration` 是 consumer 运行上限,值为 Go duration;`--max-events` 是收到 N 条后退出
的整数;`--ttl` 是服务端订阅 TTL;全局 `--timeout` 是普通请求超时秒数,不控制事件流。
正式表已有 `calendar_duration_minutes`,其 `duration` 表示分钟数。真实生成器拒绝把同一
成员再放入 Event Go duration concept,候选因此使用命令级
`run/listen/runtime-duration → duration`,不污染日历单位。新增 `event_max_events`
concept;`subscription-ttl → ttl` 只在 consume 内精确映射。秒数、TTL 和 duration
之间不转换。
### 6. subscribe_id 在 consume/status 是 flag,在 stop 是位置参数
`consume --subscribe-id` 复用已有订阅,`status --subscribe-id` 过滤一个订阅;
`stop [subscribe_id]` 却要求一个位置参数,并与 `--all` 互斥。stop 是 destructive、
`confirmation=user_required`,必须先 dry-run,再经确认使用 `--yes`。
候选新增 `event_subscription_id` concept,只覆盖 consume/status 的 flag;stop 上的
`--subscribe-id/--subscription-id` 被 block,通用 `--id` 标为 ambiguous,避免破坏性
目标被静默猜测。`--all-subscriptions → --all` 是安全的同布尔含义映射,且 dry-run 输出
与 canonical 逐字节相同。
### 7. 输出/控制参数与 Help 隐藏兼容面容易被误解
`consume --output-dir` 是每事件一个文件的目录;`--route` 是可重复的 regex 路由;
`--flatten` 改业务字段投影;`--compact` 是渲染提示。控制面还包含
`personal-event-base-url`、`stream-source-id`、`stream-ticket-url/mode`,这些 URL、ID
和 mode 角色不能靠泛化 `url/source-id/mode/directory` 猜测。
此外,`event list/status` 的 Cobra 树仍注册并隐藏内部 `--all`、`--all-editions` 等 flag,
但个人事件路径 Runtime 会明确拒绝它们。生成器禁止把真实 flag 配为 block,因此候选不
覆盖该原生 guard;这属于 Help/执行兼容面,应由命令声明清理,而不是别名表改写。
## 当前别名表可以实施的方案
1. 扩展 `user_id`、`open_conversation_id`、`search_query` 到两个精确 Event 监听入口。
2. 新增单值 `open_dingtalk_id`、事件最大条数 `event_max_events`、订阅 ID
`event_subscription_id` 三个 concept。
3. 为 6 个叶声明意图、EventKey 位置语法、过滤层、生命周期、输出目录、状态与 stop
目标的 scoped alias、block 和 ambiguous。
4. 保持所有 alias 值原样传递;不解析自然名称、不改 ID 值域、不改单位、不读取过滤
文件、不把 flag 改成位置参数。
5. 为全部 6 个 Event active 命令补 complete-command payload 模板后,再评审正式替换。
## 当前能力支持不了的事项
- 把 `--event-key`/`--subscription-id` 之类 flag 改写为位置参数;
- 把姓名或群名在底层 `consume` 中自动解析为唯一 ID;
- 自动转换 userId、openDingTalkId 与 openConversationId;
- 把单值 ID 与复数 ID 自动互转;
- 把 facade 的 `message/reaction/read/recall` 转成任意 raw EventKey 组合;
- 把 query、正则和 Filter DSL JSON 互相包装或从文件读取;
- 把秒数、分钟数、Go duration、TTL 或全局 timeout 互换;
- 自动选择 `--output-dir` 与 `--route`,或把文件路径当目录;
- 用别名表消除隐藏内部 flag 或修改 Runtime 的多事件兼容性约束;
- 在没有 complete-command 模板时直接替换正式表。
这些情况应停止并提示精确语法,或先调用 `+listen-im` 的解析层;不得为了继续监听或停止
订阅而猜测目标。
## 第一轮改造建议
第一轮建议落地 typed target、facade kind/events、消息 query、运行边界、订阅 ID、目录/
过滤角色、catalog/status/schema/stop 的低风险别名和保护。落地 PR 必须同步为以下 6 个
命令补 complete-command E2E 模板:`event +listen-im`、`event consume`、`event list`、
`event schema`、`event status`、`event stop`,覆盖 22 个 active fixture。另行清理
`list/status` 隐藏内部 flag 的声明与 Help 同源问题,不把该问题塞进别名表。
## 候选 `param_concepts.json` 改动与审核
候选文件是冻结提交正式表的完整副本,不是增量片段。相对冻结正式文件:
- 修改 3 个既有 concept 的精确 Event 命令范围;
- 新增 3 个 Event 专用 concept;
- 新增 6 个 Event command override;
- 新增 30 个审核 fixture,其中 22 个是 active alias fixture;
- `go generate ./internal/cli` 从 569 个命令作用域变为 575 个;
- 还原 Event 改动后,非目标 concept、override、fixture 与正式表结构完全相同;
- 生成 Go 差异只新增 6 个 Event 条目,command path fallback 无变化;
- 5 组代表 alias/canonical 命令退出码与完整输出逐字节相同;
- 9 组错误输入分别稳定返回 `blocked_flag` 或 `ambiguous_flag`。
候选位置:`docs/parameter-hallucination/event/param_concepts.json`。
## 验证结果与正式替换前置条件
| 验证项 | 结果 | 说明 |
|---|---|---|
| JSON 解析与真实生成器 | 通过 | `go generate ./internal/cli`,575 个命令作用域 |
| PreParse 与 alias/canonical 输出 | 通过 | listen、consume、stop-all、list、event schema 五组逐字节一致 |
| block/ambiguous | 通过 | 位置参数、自然目标、ID 角色、stop 目标等 9 组均在业务派发前停止 |
| 原生参数 | 通过 | canonical flags/positionals 保持原生;隐藏内部 flag 继续由 Runtime guard 拒绝 |
| 非目标回归 | 通过 | 非目标 JSON 结构恒等;生成 diff 仅 6 个 Event 条目;fallback 无变化 |
| `internal/cli`、`internal/pipeline` | 通过 | 隔离冻结副本执行,CLI 106.791 秒 |
| generated drift | 通过 | 双次 alias 与 Schema 装配 hash 一致 |
| Schema Catalog 政策 | 通过 | 28 产品、1166 工具 |
| 完整 `internal/app` | 未通过 | 238.083 秒;唯一产品相关失败为 complete-command 模板 |
| complete-command payload 门禁 | 未通过 | 200/206 个活跃命令已有模板;Event 缺 6 个命令、22 个 active fixture 模板 |
正式替换前必须补齐 6 个模板并重跑完整 `internal/app` 和政策门禁;未完成前,本候选只
作为完整待审核草稿。
## 分析依据
- 冻结提交:`aa4ae9a90323aa97e5cebdb5045b129f0f14e0df`,提交时间
2026-08-20 10:53:27 +08:00。
- 正式表 SHA-256:
`e41e7908c26dfdcc23636d27661d705416c001d67a6d0d5d658b1ca4bcc815c1`。
- 候选 SHA-256:
`b3d0a0ddb6fa067222742b96c28130f44a62df85e7b55d699497f15ff0e39d36`。
- 命令实现:`internal/app/event_command.go`、`internal/app/event_personal_command.go`、
`internal/app/event_listen_im.go`;运行时:`internal/event/`。
- Skill:dingtalk-event 根 Skill,以及 EventKey、OA、生命周期、订阅运维四份 reference。
- Schema 来源:同一冻结二进制运行时声明组装;未使用历史或固定 Schema Catalog。
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,188 @@
# HRBrain 产品 CLI 参数幻觉分析
## 结论摘要
本分析以线上 `origin/main` 冻结提交
`aa4ae9a90323aa97e5cebdb5045b129f0f14e0df` 为唯一基线,使用该提交重新构建的
`dws`、运行时 Schema、官方 Cobra 树、HRBrain 实现与测试、`dingtalk-misc` 中的
HRBrain Skill,以及冻结正式 `internal/cli/param_concepts.json`。未使用固定 Catalog、历史
badcase、用户 Shortcut 或已安装插件,也没有修改当前工作区正式别名表。
HRBrain 有 11 个 Agent 可见叶,覆盖人才池 3 个、员工档案 5 个、人才搜索 3 个。Skill、Help、
Schema 和实现对公开命令、canonical flag、必填性与 JSON/逗号分隔外形基本一致。候选新增 3 个
概念(人才池编码、单工号、多工号),并把既有搜索、页码和每页条数概念按精确命令扩展到
HRBrain;为 11 个叶增加 `scope_strict` override,其中无业务参数的 `search fields` 只做保护。
候选共 59 个 fixture:31 个 active、28 个 guard。生成结果为 86 个 alias、142 个 blocked、
64 个 ambiguous,命令作用域从 569 增至 580,command path fallback 保持 34。
候选已通过生成、31 组 alias/canonical payload 等价、28 组 block/ambiguous dispatch 前保护、
JSON 外层校验、真实全局参数保持原生、非目标结构恒等、HRBrain 专项测试、`internal/cli`、
`internal/pipeline`、embedded fixture delivery、generated drift 和 Schema Catalog 政策。
候选单独替换时,完整 `internal/app` 会按政策失败:10 个含 active alias 的 HRBrain 命令尚未在
`paramAliasCompleteCommands` 中登记完整 canonical payload 模板。隔离副本临时补齐这 10 条模板后,
payload-equivalence 专项和完整 `internal/app`(237.773 秒)均通过,未发现第二处隐藏失败。因此
候选结论是**条件通过**:正式落地必须把候选与 10 条完整命令模板作为同一变更提交并重跑全门禁,
不能只替换 JSON。
## 参数问题
### 1. 员工工号在单值与多值命令中命名不一致
`profile metadata/query/career/performance` 使用单值 `--work-no`;`profile labels` 使用逗号分隔
多值 `--staff-ids`。Agent 容易在单值命令生成 `--employee-id`、`--staff-id`、`--job-number`,
也容易把单值 `--work-no` 直接用于多人工号列表。
候选分别建立 `hrbrain_work_no` 与 `hrbrain_work_nos`,只把同角色、同值域、同单复数且可原样传递
的名称归一化。单值与多值之间,以及 userId/openDingTalkId/手机号与工号之间,全部 block 或
ambiguous,绝不做自动互转。
### 2. 人才池编码容易与名称、通用 ID 或员工标识混用
`talent-pool detail/employees` 和 `search employees` 的 `--pool-code` 都表示人才池编码;
`talent-pool list --keyword` 才是人才池名称关键词。`pool-id`、裸 `id`、`pool-name` 看似接近,
但冻结接口没有证明其值域与 poolCode 等价。
候选建立 `hrbrain_pool_code`,只接受明确的 code 同义写法,并在精确命令绑定 canonical
`--pool-code`。名称、通用 ID、员工 ID 保持 block/ambiguous;不会把名称搜索伪装成编码查询。
### 3. 搜索与分页参数存在常见拼写差异,但必须限制命令范围
`talent-pool list` 和 `search employees` 的 `--keyword` 是搜索文本;4 个列表/搜索叶使用
`--page` 与 `--page-size`。这些参数可安全吸收明确同义词,但 `query` 在 `profile query` 中是
命令动作,JSON `data-queries` 也不是搜索关键字;`limit`、`offset`、`cursor` 的语义和单位不能
一概等同。
候选复用既有 `search_query`、`page_number`、`pagination_size`,仅追加已审核 HRBrain 精确命令。
会改变分页模型或单位的名称被保护,不扩散到 detail、profile 或无参数的 `search fields`。
### 4. JSON 与逗号分隔参数容易被错误包装或互换
`profile query --data-queries` 必须是非空 JSON 数组;`search employees-structured
--origin-json` 必须是 JSON object,业务 `--fields` 必须是非空 JSON 数组;`--labels`、
`--staff-ids`、`--order-by` 则是逗号分隔字符串。别名表只能改 flag 名,不能把 CSV 拆成 JSON、
把对象包成数组,或补齐数组成员字段。
运行时能拒绝非法 JSON、空数组和错误的 object/array 外形,但当前不会逐项验证
`data-queries` 的 `modelCode/fields` 或 structured `fields` 的成员结构。候选只做名称保护,不声称
完成值转换或深层 Schema 校验。
### 5. `--fields` 在同产品内存在业务 flag 与全局 flag 的角色碰撞
`search employees-structured --fields` 是必填业务 JSON 数组,会进入接口 payload;其余 HRBrain
叶继承的 `--fields` 是全局输出字段投影。两者拼写完全相同,但含义、值格式和落点不同。
中央参数字典不能在解析前仅凭 flag 名区分用户意图,也不能把一个真实 flag 重写成另一个角色。
候选保持两种原生行为,不为 `fields/columns/select` 增加跨角色 alias;报告和 Skill 必须继续明确
structured 叶需要 JSON 数组,而其他叶的 `--fields` 只是输出投影。
### 6. 数字分页只校验类型,没有正数范围契约
`--page`、`--page-size` 是 int,默认分别为 1 和 20;非整数由 Cobra 拒绝,但 `--page 0` 会通过
并进入 payload。别名表不能新增数值范围,也不能安全地把 offset/cursor 换算成页码。
候选不修改数值,只做同单位、同模型的名称归一化。若业务要求正数,必须在 leaf Contract/Runtime
增加显式范围约束并补测试,不能靠 alias 表假装已校验。
## 当前别名表可以实施的方案
1. 新增 `hrbrain_pool_code`,绑定人才池详情、人才池人员与简单人才搜索中的 `--pool-code`。
2. 新增 `hrbrain_work_no`,绑定 4 个单员工档案叶的 `--work-no`。
3. 新增 `hrbrain_work_nos`,只绑定标签查询的逗号分隔 `--staff-ids`。
4. 将既有 `search_query` 精确扩展到人才池列表和简单人才搜索。
5. 将既有 `page_number`、`pagination_size` 精确扩展到 4 个列表/搜索叶。
6. 为 11 个叶增加 `scope_strict` override,用 block/ambiguous 隔离 ID 值域、单复数、JSON/CSV、
分页模型、字段角色和不存在的身份参数。
7. 保持所有真实 canonical flag 和全局 flag 原生,尤其保持 structured 业务 `--fields` 与其他叶
全局输出 `--fields` 的各自行为。
8. 正式落地时同步登记 10 条完整命令 canonical payload 模板,使 active fixture 进入最终等价门禁。
## 当前能力支持不了的事项
- 在工号、userId、openDingTalkId、手机号之间查询或转换;
- 将单工号和逗号分隔多工号自动拆分、合并或去重;
- 从人才池名称查出 poolCode,或把通用 pool ID 转换为 poolCode;
- 把 CSV 的 labels/staff-ids/order-by 转成 JSON,或反向转换;
- 给 data-queries、origin-json、structured fields 自动补 JSON 包装或成员字段;
- 深层验证 `data-queries[].modelCode/fields` 和 structured `fields[]` 的业务结构;
- 在解析前消除业务 `--fields` 与全局输出 `--fields` 的同名角色碰撞;
- 把 offset/cursor/limit 换算成 page/page-size;
- 约束 page/page-size 必须为正数;
- 根据姓名、部门或手机号先查人再填工号;这属于 `aisearch/contact` 编排,不是别名改写;
- 绕过人才池查看权限、档案权限或登录 profile 边界。
## 第一轮改造建议
第一轮可落地 3 个专属概念、3 个既有概念的精确命令扩展、11 个严格 override 和 59 个 fixture,
但必须同时补齐 10 条 `paramAliasCompleteCommands` 模板。这样既能吸收同值域、同角色、同单位的
常见拼写,又能在 dispatch 前阻止工号值域、单复数、人才池名称/编码、JSON/CSV、分页模型和
fields 角色误用。JSON 深层结构、正数范围和跨产品身份解析应作为 Runtime/Contract 或编排层后续,
不能扩大本轮 alias 范围。
## 候选 `param_concepts.json` 改动与审核
- 新增 3 个 concept:`hrbrain_pool_code`、`hrbrain_work_no`、`hrbrain_work_nos`;
- 扩展既有 `search_query`、`page_number`、`pagination_size` 的 HRBrain 精确命令范围;
- 新增 11 个 `scope_strict` command override;
- 新增 59 个 fixture:31 active、28 guard;active 覆盖 10 个有业务参数的叶;
- `search fields` 没有业务参数,只做 guard,不制造 alias;
- `go generate ./internal/cli` 的命令作用域 569→580;HRBrain 生成 86 alias、142 blocked、
64 ambiguous;command path fallback 仍为 34;
- 删除 HRBrain 改动后,非目标 concept、override、fixture 与冻结正式表结构恒等;
- 自动 alias 的 source 均不是该精确命令中具有不同语义的真实 flag,target 均为真实 canonical flag;
- 31 个 active case 全部与 canonical payload 等价,28 个 guard 均在 dispatch 前停止;
- 非法 JSON/空数组/错误外形和非整数分页被 Runtime/Cobra 拒绝;`page=0` 仍会进入 payload,已明确
列为 Runtime 范围缺口;
- 当前工作区正式别名表未被修改;候选是基于冻结正式表的完整独立草稿,不累计其他产品候选。
审核结论:规则本身范围正确且行为验证通过,但仓库政策要求 active alias 命令具备完整 canonical
payload 模板。候选位置:`docs/parameter-hallucination/hrbrain/param_concepts.json`。正式落地状态为
**条件通过**,必须与 10 条模板一起提交。
## 验证结果与正式替换前置条件
| 验证项 | 结果 | 说明 |
|---|---|---|
| JSON 解析与生成器 | 通过 | 580 个命令作用域;HRBrain 86 alias、142 blocked、64 ambiguous |
| active alias/canonical | 通过 | 31 组 PreParse 后 canonical argv 与 payload 等价 |
| block/ambiguous | 通过 | 28 组均在 HRBrain dispatch 前停止,dispatch 0 |
| JSON/类型校验 | 通过(有限) | 5 类非法值拒绝;不覆盖数组成员深层结构和正数范围 |
| 全局参数原生行为 | 通过 | hidden/global flag 未被覆盖;fields 角色按命令保持原生 |
| 非目标回归 | 通过 | 正式 JSON 非目标结构恒等;generated diff 仅 HRBrain;fallback 不变 |
| HRBrain 专项测试 | 通过 | `TestHrbrain*`,0.948 秒 |
| embedded fixture delivery | 通过 | fixture 经最终嵌入加载路径生效,0.792 秒 |
| `internal/cli`、`internal/pipeline` | 通过 | CLI 72.963 秒;pipeline 0.490 秒 |
| generated drift | 通过 | 参数别名与 Schema 双次装配确定 |
| Schema Catalog 政策 | 通过 | 28 产品、1166 工具;Runtime confirmation truth 通过 |
| 完整应用(候选单独) | 阻断 | 10 个 active 命令缺少 complete-command payload 模板 |
| 模板补齐验证 | 通过 | 临时补 10 条模板后 payload-equivalence 专项通过,1.613 秒 |
| 完整应用(候选+模板) | 通过 | `go test ./internal/app -count=1`,237.773 秒 |
正式替换前必须在 `internal/app/param_alias_payload_equivalence_test.go` 为以下 10 个命令登记完整
canonical payload 模板,并与候选一起评审:`talent-pool list/detail/employees`、
`profile metadata/query/labels/career/performance`、`search employees/employees-structured`。
落地后应从同一基线合并所有获批产品差异,而不是依次覆盖独立候选,并重跑生成、PreParse、
payload equivalence、全量应用和全部仓库政策。
## 分析依据
- 冻结提交:`aa4ae9a90323aa97e5cebdb5045b129f0f14e0df`,提交时间
2026-08-20 10:53:27 +08:00;
- 正式表 SHA-256:
`e41e7908c26dfdcc23636d27661d705416c001d67a6d0d5d658b1ca4bcc815c1`;
- 候选 SHA-256:
`ebae9af7df01928031bf444e829e10232e97b058d5b114774a416445adacf55c`;
- 命令实现与声明:`internal/helpers/hrbrain.go`;
- 专项测试:`internal/helpers/hrbrain_test.go`;
- Skill:`dingtalk-misc/references/hrbrain.md`;
- Schema:同一冻结二进制通过 `ResolveSchemaBuild` 运行时声明组装;
- 官方树边界:11 个 Agent 可见叶,无用户 Shortcut 或安装插件;
- 隔离副本:`/private/tmp/dws-param-analysis-aa4ae9a90323`;
- 候选验证只使用 dry-run/mock/测试 seam,不访问或修改真实员工档案、人才池与组织数据;
- 明确未使用:固定 Catalog、历史 badcase、评测工作簿、用户 Shortcut、已安装插件。
## 可复用分析流程
对同时包含标识符、列表搜索、分页、JSON 与 CSV 的产品,先按“实体、角色、值域、单复数、单位、
值格式”拆分,再检查同名 flag 是否在业务层和全局层承担不同角色。只有原值可直接传递的同义名称
进入精确命令 alias;跨值域、跨单复数、跨分页模型和结构转换全部 fail closed。最后必须用完整
canonical payload 模板验证 active fixture,而不能只证明 PreParse 文本发生了改写。
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,107 @@
# dws_multi-im-optimization 全量实验 Badcase 审计
审计日期:2026-08-05
数据目录:`/Users/hyz/works/data/dws_multi-im-optimization`
目标:区分真实参数幻觉、Shortcut 命令名幻觉、子命令层级错误、错误类型误报和非幻觉运行时错误。
## 数据口径
- 扫描了目录下全部 480 个 `turn_*.rounds.json`、46 个候选 run 级 report 以及对应 manifest。
- 同一实验存在原始 rounds 时只以 rounds 为准;没有原始轨迹时才读取 `report_*_runN.json`。聚合 report、Markdown、HTML 和 rawEvents 不重复计数。
- 最终得到 6,109 条去重后的 DWS Bash 调用,覆盖 1,482 个实验/模式/Case/Run 组合。
- 其中 632 条有非零退出或明确 unknown/ambiguous 错误标记;其余成功调用不进入 badcase 计数,但用于确认 Case 覆盖。
- 一条 Bash 命令包含 `||` 等多个 DWS 探测时仍按一次工具调用计数;链内额外命令名作为补充证据记录,不虚增主要调用数。
## 版本核验
| 数据组 | 命令面依据 | 可信度 |
|---|---|---|
| `三组对比结果/dws_分支` | 报告明确记录 `b8b55834402c...`,已重建该提交 Cobra 面 | 精确 |
| `20260804_ee943...` 与对应 audit | manifest 的 `ee943d9b3f58...` | 精确 |
| `f050fbde` 系列及 `dws_f050fb` | manifest/目录记录 `f050fbdebca9...` | 精确 |
| `audit_...987c63d...` | manifest 的 `987c63d99cc4...` | 精确 |
| `三组对比结果/dws_main` | 采用实验日期附近 beta.2 main release head `18778704`,并以轨迹真实错误复核 | 近似基线 |
`b8b55834`、`ee943d9b`、`f050fbde` 对 51 个目标命令都只有 50 个,统一缺少 `chat +chat-list`。因此实验不能证明 `+chat-list` 的参数表现。
## 总体结果
| 分类 | 调用数 | 判断 |
|---|---:|---|
| 真实参数名幻觉 | 102 | 命令 leaf 在对应实验版本真实存在,但错误 flag 不存在 |
| Shortcut 命令名幻觉 | 16 | 包含 10 条同时携带非法 flag 的复合调用 |
| 兼容分派节点误当可执行 leaf | 192 | 主要是历史 `chat group search`,带 flag 先报 unknown flag,去掉 flag 才暴露 ambiguous |
| 其他子命令路径幻觉 | 25 | 其中 15 条同时被误报为 unknown flag |
| Schema 查询路径不存在 | 25 | 错误 leaf 的 Schema 探测证据,不是业务执行参数错误 |
| 参数值解析歧义 | 4 | 同名群等 resolution_ambiguous,不是参数名幻觉 |
| 后端业务错误 | 134 | 参数已通过本地解析,失败发生在接口/业务层 |
| 确认门禁 | 20 | 预期安全行为 |
| 其他运行时/探测/非 Chat 错误 | 116 | 排除出本次 Chat 参数与命令名治理 |
上述前五类合计 360 条命令路径或参数幻觉调用,涉及 238 个 Case-Run。
## `unknown flag` 不能直接等于参数幻觉
全量共有 251 条调用输出 `unknown flag`。按“先解析完整命令路径,再校验 flag”的顺序复核后:
- 138 条应首先归为不存在的 Shortcut、错误子命令层级或兼容分派节点;
- 102 条才是真实参数名幻觉;
- 其余 11 条属于非 Chat 产品或探测边界。
典型问题是历史 `dws chat group search --keyword/...`:当时该路径只是无稳定 leaf 参数契约的兼容分派节点,带 flag 先报 `unknown flag`;无 flag 才会返回 `ambiguous command "search"`,Schema 也返回 `unknown runtime schema path "chat group search"`。当前 main 已把它升级为真实 leaf,并接受 `query/keyword/name`,因此治理方式是保留当前子命令判断修复与回归测试,不再为它增加 Shortcut fallback。
## 51 个目标 Shortcut 中捞到的参数 badcase
37 条幻觉调用与 51 子集精确相交,分布如下:
| 命令 | 次数 | 错误 flag | 结论与建议 |
|---|---:|---|---|
| `+chat-update` | 10 | `id/chat-id/conversation-id/open-conversation-id`,且部分还有 `title/new-title` | CID 拼法与 `title/new-title→name` 可命令级处理;generic `id` 需要值域保护 |
| `+flag-list` | 9 | `limit/max/max-results/max-size` | 只确认 `limit→size` 严格等价;`max*` 不自动吞掉 |
| `+chat-members-list` | 4 | `chat-id/id/query` | `chat-id/id→conversation-id` 可;`query` 是当前不支持的成员过滤能力 |
| `+conversation-set-top` | 4 | `open-conversation-id/chat-ids/groups/top` | 单/列表 CID 可 scoped;`groups/top/set-top` 不可一对一改写 |
| `+chat-members-get` | 3 | `conversation-id/group`,并伴随 `open-dingtalk-ids` | `conversation-id→id`、`open-dingtalk-ids→users` 可;群名不能直接当 CID |
| `+chat-get-by-id` | 2 | `group`,值为 CID | 不能兜底到数字 `group-id`,必须 block |
| `+messages-list` | 2 | `count/max-results` | 页大小/总量口径不明;优先路由 `+chat-messages` |
| `+messages-reply` | 2 | `group/msg-id` | `msg-id→ref-msg-id` 可;群名→CID 需要 resolver,不能靠别名 |
| `+messages-recall` | 1 | `message-id` | 当前 main 已作为隐藏原生兼容参数,保持原生即可 |
这里的 37 条只统计请求命令本身属于 51 子集。若一个不存在的命令名可能映射到其中某个命令,但命令名本身有多个合理目标,则放在 Shortcut 幻觉治理,不强行归入参数统计。
## Shortcut 命令名幻觉与兜底建议
| 幻觉命令 | 观察 | 当前状态 | 建议 |
|---|---|---|---|
| `+group-search` | 4 条实际调用,另有同类探测 | 已有 rewrite 到 `+chat-search` | 保持;目标参数仍由真实命令校验 |
| `+search-group` | 2 条 | 当前已是 `+chat-search` 原生命令别名 | 不增加 fallback |
| `+send-text` | 1 条 | 已有 messages-send/dm/send-to-group ambiguous | 保持停止选路,写操作不自动猜 |
| `+send-single` | 1 条 | 已有 dm/messages-send ambiguous | 保持停止选路 |
| `+conversation-detail` | 1 条,意图是单会话详情 | 未覆盖 | 可安全 rewrite 到只读 `+conversation-info` |
| `+bot-list` | 1 条,意图是查看某群机器人 | 未覆盖 | 可安全 rewrite 到只读 `+chat-bots`;错误 flag 继续由目标暴露 |
| `+conversation-category-list` | 1 条,链内还猜了 `+conversation-category` | 未覆盖 | ambiguous:`+category-list` / `+category-list-conversations` |
| `+conversation-group-list` | 1 条 | 未覆盖 | ambiguous:`+category-list-conversations` / `+conversation-list` |
| `+list-my-groups` | 1 条 | 未覆盖 | ambiguous:`+my-groups` / `+chat-list-mine` / 当前 `+chat-list` |
| `+help` | 2 条 | 未覆盖 | 不建业务 fallback;明确提示 `dws chat --help` |
| `+messages-send` | 1 条出现在旧 `987c63d` 版本 | 当前是原生命令 | 版本升级已解决,不增加 fallback |
新增 rewrite 只建议两个:`+conversation-detail → +conversation-info`、`+bot-list → +chat-bots`。三条 list/category 名称没有唯一语义,应记录为 ambiguous,而不是根据单个 Case query 固化成全局 rewrite。
## 对兜底层的要求
1. 先解析完整命令路径;不存在的 path 不得先消费其 flag 并返回 `unknown_flag`。
2. Shortcut rewrite 只解决命令名,不同时偷改参数;目标命令必须继续执行自己的参数校验。
3. 写操作只要发送身份、接收者类型或能力边界不唯一,一律 ambiguous。
4. 当前主分支已真实存在的命令或 alias 不重复进入 fallback 表。
5. 实验中的组合问题保留主次:主分类是命令名/层级,目标映射后的非法 flag 作为次要证据进入参数测试。
6. 历史版本问题与当前问题分开:已经由真实命令面或原生兼容参数解决的,不再追加中央兜底。
## 测试建议
- 对 16 条 Shortcut 幻觉逐条建立 path-only 回放,先断言 rewrite/ambiguous/原生命令/明确拒绝。
- 对 102 条真实参数调用做去重 fixture;同一命令/flag/预期只保留一条结构化断言,原始 Case 引用全部保留。
- 对 138 条误报建立错误优先级测试:不存在 path 必须返回 command/subcommand/fallback 结果,而不是 unknown flag。
- 对 `chat group search` 建历史回归:当前 main 应作为真实 leaf 接受 `query/keyword/name`,Schema 查询也应命中。
- 对 37 条 51 子集 badcase 建 param_concepts/native/block/unsupported 四类断言。
- 对没有实验样本的 `+chat-list` 用当前 Cobra/Schema 合成测试补齐。
完整 632 条异常调用、360 条幻觉明细、102 条真实参数明细和 16 条 Shortcut 命令名明细见 badcase 审计工作簿。
@@ -0,0 +1,121 @@
# IM Shortcut 命令名与参数幻觉合并治理方案
日期:2026-08-05
输入:51 个增量 Shortcut 静态审计 + `dws_multi-im-optimization` 全量实验 badcase 审计
本文件只定义实施与测试顺序,不在本轮修改运行时配置。
## 总原则
治理分成两层,顺序固定:
1. **命令层**:解析完整 path,处理真实命令/原生 alias/安全 rewrite/ambiguous/unknown。
2. **参数层**:只有命令 leaf 已唯一确定后,才运行原生 flag、`param_concepts`、block/ambiguous 和 Cobra 校验。
命令 fallback 不同时修改 flag;参数 fallback 不创建命令。这样才能避免“不存在的 Shortcut 带一个非法 flag,却先返回 unknown flag”的错误类型倒置。
## 第一阶段:先修错误优先级和命令名兜底
### 保持现有规则
- `+group-search → +chat-search` rewrite;
- `+search-group` 当前原生 alias;
- `+send-text`、`+send-single` 等写操作 ambiguous;
- 当前主分支真实 `chat group search` leaf,不再加 fallback。
### 建议新增 rewrite
| 来源 | 目标 | 准入理由 |
|---|---|---|
| `chat +conversation-detail` | `chat +conversation-info` | 单个会话详情、只读、唯一目标、能力与安全等级一致 |
| `chat +bot-list` | `chat +chat-bots` | 指定群机器人列表、只读、唯一目标;参数保留给目标校验 |
### 建议新增 ambiguous
| 来源 | 候选 |
|---|---|
| `chat +conversation-category-list` | `chat +category-list`、`chat +category-list-conversations` |
| `chat +conversation-group-list` | `chat +category-list-conversations`、`chat +conversation-list` |
| `chat +list-my-groups` | `chat +my-groups`、`chat +chat-list-mine`、`chat +chat-list` |
`chat +help` 不进入业务 fallback,直接提示 `dws chat --help`。
### 第一阶段测试
- 回放全部 16 条 Shortcut 命令名调用;
- 对 rewrite 断言只改 argv path、flag/value 顺序不变;
- 对 ambiguous 断言没有执行任何候选;
- 对不存在 path + 非法 flag 断言命令层结果优先;
- 对当前原生命令/alias 断言不触发 fallback。
## 第二阶段:扩展安全参数 concept
### 2.1 会话 ID
- 扩展 `open_conversation_id.commands` 到单一稳定 CID 角色的 51 子集;
- 对真实 `--id` 使用命令级 `bind`;
- 对 `+chat-members-list`、`+chat-update` 使用 scoped aliases,不把 name-or-ID 的 `group` 当全局稳定 CID;
- 对 `+chat-get-by-id` block 所有 CID 拼法;
- forward 类 src/dest 双角色对 generic CID 返回 ambiguous。
### 2.2 消息 ID
- 扩展 `open_message_id/open_message_ids` 到单角色 emoji、资源、Pin/Top 等命令;
- `+messages-reply` 只在命令内将 `msg-id/open-message-id` 归到引用消息;
- forward/topic/combine 保留 source 和单复数;
- bot recall 的 `keys` block 所有消息 ID 拼法。
### 2.3 用户与机器人
- mixed userId/openDingTalkId 参数只做命令级绑定;
- applicant/inviter、成员/群主、发送者/接收者不建立跨角色全局别名;
- `robot_code` 与 `open_bot_id` 分别扩围,并双向 block。
### 第二阶段测试
- 对每条新 alias 验证同实体、同角色、同基数、同值域、值原样传递;
- 对写命令验证归一化后目标、消息角色、确认门禁和下层 argv 不变;
- 对 block/ambiguous 验证不会进入 Runtime/MCP;
- 对原生隐藏兼容参数验证直接走 Cobra,不经中央别名。
## 第三阶段:只增加严格等价的分页/时间项
- `+flag-list limit → size`;
- `+messages-list start → time`;
- `+messages-list` 的 `before/before-time/end/page-all/count` 进入 block/unsupported;
- `+conversation-set-top` 只处理单/列表 CID 的明确拼法,`top/set-top` 不映射到反向语义 `off`;
- `+chat-list` 保持原生 `page-size/limit` 与 `page-token/cursor`。
这一步必须在前两阶段稳定后再做,避免分页同义词吞掉真实能力差异。
## 第四阶段:Schema/Skill 选路与评测
- 保持 51 个 leaf 全部发布到 Runtime Schema;
- 不把 51 条完整参数复制到根 Skill;
- 对高风险写命令、数字 groupId/CID、机器人 keys 和 source/dest 角色补 intent-guide 提示;
- 为 43 个没有在 Skill 精确点名的命令增加按需 leaf 发现/shortcut list 选路 fixture;
- 评测单独统计命令名幻觉、真实参数名幻觉、路径误报和参数值歧义,不能把所有 unknown flag 合并。
## 预计收益
- 直接消除当前未覆盖且唯一等价的两个 Shortcut 命令名;
- 对三类不唯一 list/category 名称从“误执行风险”降为可解释消歧;
- 覆盖 37 条与 51 子集直接相关的实验参数调用中的严格等价部分;
- 阻止数字 groupId/CID、messageId/processQueryKey、robotCode/openBotId 和单复数等高风险误映射;
- 让 138 条历史 `unknown flag` 路径误报回到正确的命令层错误。
## 不应承诺的效果
别名表不能解决群名查询、ID 值转换、单复数值变换、一对多参数生成、自动翻页、角色猜测或业务接口失败。此类 case 只能通过 resolver 型 Shortcut、真实命令能力、Skill 选路、明确消歧或后端修复处理。
## 完成门禁
实施后至少运行:
```text
go generate ./internal/cli
./scripts/policy/check-generated-drift.sh
./scripts/policy/check-schema-catalog.sh
DWS_PACKAGE_VERSION=0.0.0-test go test ./...
```
并追加三组聚焦测试:全部命令名 badcase、全部 51 子集参数 badcase、全部 path-before-flag 错误优先级 badcase。由于实验版本没有 `+chat-list`,还要单独补该命令的当前合成测试。
@@ -0,0 +1,109 @@
# IM 51 个增量 Shortcut 参数幻觉静态审计
审计日期:2026-08-05
审计基线:`fix/param-hallucination` 当前工作树,HEAD `b1d422dc`(已包含当时最新 `main`)
审计方法:`specs/product-cli-param-hallucination-analysis-spec.md`
## 结论
1. 当前主分支的 51 个目标 Shortcut 均是可执行 Cobra leaf,也都能从最终 Runtime Schema 解析到;51/51 的公开 Cobra 参数与 Schema 参数完全一致。
2. 这 51 个命令当前都没有生成的 `param_concepts` 兜底项,即直接覆盖为 0/51。已有 concept 定义可以复用,但 `commands` 范围尚未包含这批 Shortcut。
3. 51 个命令中,Chat Skill 只精确点名 8 个,另外 43 个没有逐条展开。根 Skill 明确把完整低频清单交给 Runtime Schema/Catalog,并提供 `dws shortcut list --service chat` 最后回退,因此这不是 43 个公开契约缺失;它说明低频选路高度依赖正确的 leaf 发现。
4. 四个命令已经存在隐藏的原生兼容参数,不应再伪装成中央别名能力:
- `+chat-members-list`:`--chat`、`--open-conversation-id`
- `+messages-list`:`--conversation-id`、`--id`、`--size`
- `+messages-recall`:`--chat`、`--group`、`--id`、`--message-id`、`--message-ids`
- `+messages-resource-url`:`--msg-id`、`--open-message-id`
5. 可安全落到现有别名表能力的主要是“同实体、同角色、同基数、同值域、值原样传递”的精确命令级映射。数字 groupId/CID 转换、群名解析、单复数转换、源/目标角色选择、`before → time + direction` 和 `page-all` 翻页都不应由别名表完成。
## 八类静态问题
| 问题 | 影响 | 结论 |
|---|---:|---|
| 会话 ID 参数名碎片化且 `--group` 语义漂移 | 36 个命令表现 | 单角色稳定 CID 可扩展 `open_conversation_id`;群名/CID 混合与双角色必须 scoped/block |
| 数字群号与 openConversationId 值域冲突 | 1 个命令 | `+chat-get-by-id --group-id` 不得接收 CID 别名,只能拦截并提示换命令/先转换 |
| 消息 ID 名称、角色与 processQueryKey 混杂 | 13 个命令表现 | 普通消息单角色可归一;ref/src/keys 必须保留角色或隔离 |
| 用户 ID 值域、角色和单复数混杂 | 8 个命令表现 | mixed-ID 参数可命令级绑定;applicant/inviter 等多角色不能猜 |
| robotCode 与 openBotId 混淆 | 6 个命令表现 | 扩展两个独立 concept,并双向 block 交叉拼法 |
| 单值/列表与分页口径混用 | 5 个命令表现 | 只落严格等价项;单复数、page/cursor、count/size 不自动互转 |
| 时间、方向与全量能力不能单 flag 改写 | 1 个命令 | `start→time` 可审;`before/page-all` 超出现有能力 |
| 低频能力依赖 Schema 发现 | 全部 51 个 | 补选择与回归,不把 51 条完整参数复制进根 Skill |
完整逐命令事实、问题明细和不可解决项见静态审计工作簿。
## 推荐的参数兜底设计
### 1. 扩展已有会话 ID concept
把审核后只有一个稳定 CID 角色的命令加入 `open_conversation_id.commands`,包括:
- 会话分类增加/移除会话;
- 加机器人、移机器人、按 ID 查成员;
- 群审批、禁言、退出、身份移除、转让群主、头像和设置;
- 会话清消息、清红点、隐藏、已读/未读、免打扰;
- 消息标记、emoji/文字表情、机器人群发/撤回、资源、Pin/Top。
真实参数是宽泛 `--id` 的命令使用 `command_overrides.bind` 明确绑定,不把 `id` 加入全局 concept。
### 2. 对混合 `--group` 做命令级处理
- `+chat-members-list`:保留原生 `--group` 的群名/CID resolver;只增加 `chat-id/id → conversation-id`,`--query` 不是成员过滤能力,需 block/unsupported。
- `+chat-update`:`conversation-id/open-conversation-id/chat-id → group` 可以原样传 CID;`id` 过于宽泛,建议先 block,除非增加 CID 本地值域校验。`title/new-title → name` 可作为同命令内严格等价的名称字段。
### 3. 严格隔离数字群号
`+chat-get-by-id --group-id` 的值是数字 groupId。建议对 `group`、`conversation-id`、`chat-id`、`open-conversation-id`、`id` 全部配置 block。系统只能告诉调用方:传数字群号,或改用接受 openConversationId 的 leaf;不能自动查询转换。
### 4. 扩展消息 ID concepts,保留角色
- 普通单消息角色:将会话已读边界、emoji、资源、Pin/Top 等命令纳入 `open_message_id`。
- 消息列表:单值与 `open_message_ids` 分开,不启用全局单复数规则。
- 引用回复:`+messages-reply` 可做 `msg-id/open-message-id → ref-msg-id`,但不把引用角色抹成全局普通消息 ID。
- 转发:只在精确命令中将角色明确的拼法映射到 `msg-id/src-msg-id/msg-ids`;存在源/目标双会话时,generic `chat-id` 返回 ambiguous。
- 机器人撤回:`keys` 是 processQueryKey,不是 openMessageId;block 所有 message-id 拼法。
### 5. 用户与机器人值域保护
- `+chat-members-get --users` 只接受 openDingTalkId 列表,可 bind 到 `open_dingtalk_ids`,并将 `open-dingtalk-ids → users`。
- mixed-ID 列表或单值只做命令级 scoped alias,不提升为全局等价。
- `applicant/inviter`、成员/群主、发送者/接收者等不同业务角色保持隔离;generic `user-id` 有多个目标时返回 ambiguous。
- `robot-code` 与 `bot-id(openBotId)` 各自扩展命令范围,并在对方命令 block。
### 6. 只落严格等价的分页和时间项
- `+flag-list`:`limit → size` 可以命令级归一;`max/max-results/max-size` 不足以证明是“每页数量”。
- `+messages-list`:`start → time` 可以原样传;`before/before-time/end` 需要方向或边界重写,`page-all` 需要循环,均 block 并提示使用 `+chat-messages`。
- `+chat-list` 已真实支持 `page-size/limit` 与 `page-token/cursor`,保持原生即可。
## 明确不做的自动兜底
- 群名查询成 openConversationId;
- 数字 groupId 与 CID 互转;
- 单值与列表拆分/合并;
- `before` 生成 `time + direction older`;
- `page-all` 自动翻页并伪造完整性;
- 在 applicant/inviter、src/dest 等多个角色中猜一个;
- 把机器人 processQueryKey 当 openMessageId;
- 把资源输出文件与下载目录互换。
## 版本边界
实验目录里确认过提交的二进制不是严格的 51/51:`b8b55834`、`ee943d9b`、`f050fbde` 的真实 Cobra 面都只含 50 个目标 Shortcut,缺少 `chat +chat-list`。`+chat-list` 因此只有当前静态审计证据,没有对应实验 badcase 证据;后续要用合成 CLI/单元测试覆盖,不能把它计入“实验已验证”。
## 验证要求
1. 每条 alias 验证来源不是命令真实 flag、目标是真实 flag、值原样传递。
2. 对每个 concept 验证单值/列表、userId/openDingTalkId、robotCode/openBotId、groupId/CID 不交叉。
3. 写命令验证归一化前后目标对象、消息角色、确认门禁和最终参数组装不变。
4. 原生隐藏兼容 flag 直接走 Cobra,不经中央别名二次改写。
5. 为 block/ambiguous 断言错误在本地发生且给出正确 leaf/参数提示。
6. 对 51 个 leaf 跑 Schema/Help 一致性、生成漂移和全部静态 fixture。
## 事实来源
- `app.NewRootCommand()` 构建的当前 Cobra 命令树和 flag;
- `DeliverySchemaAllPayloadForTest()` 生成的最终 Runtime Schema;
- `skills/multi/dingtalk-chat`;
- `internal/cli/param_concepts.json` 与生成 lookup;
- `internal/cli/command_path_fallbacks.json`;
- 具体 Shortcut 的声明、约束与实现。
@@ -0,0 +1,283 @@
# DWS Chat / IM 参数问题分析与兜底方案(第二轮全量审计)
> 分析日期:2026-07-27
> 分析分支:`fix/param-hallucination`
> 分析基线:`0ef1b73`
> 分析对象:DWS Chat / IM 产品的 CLI 参数
> 对应明细:`im_cli_param_hallucination_analysis_20260727_v2.xlsx`
## 1. 结论摘要
本轮从当前 DWS 的真实命令树出发,对 Chat / IM 产品进行了第二轮全参数审计。分析覆盖 148 个目标命令、110 种可见参数名,并进一步检查了 211 条 Cobra 可执行路径。
结论是:Chat / IM 的参数问题不只集中在会话、用户、消息等业务标识符上,还包括分页、搜索词、时间窗口、文件路径、单复数、参数拼写格式以及同名参数值域冲突。第一轮识别了 7 类主要问题,本轮补充 8 类,合计形成 15 类产品级参数问题。
这些问题需要分成三种方式处理:
1. **值不变、目标唯一的一对一改名**:适合通过 `param_concepts.json` 的 concept 或命令级 alias 兜底;
2. **可能选错目标、改变单复数或丢失信息的输入**:应使用 block/ambiguous 保护性拦截;
3. **需要查数据、转换参数值、补齐多个参数或理解值域的场景**:当前别名体系不能处理,应由命令编排、值规范化或 Schema 参数约束解决。
因此,不能用“是否成功改成了一个真实参数名”作为唯一判断标准。参数名归一成功只说明 Cobra 可以继续解析,不代表业务语义、参数基数和值域一定正确。
### 1.1 量化结果
| 分析项 | 结果 | 说明 |
|---|---:|---|
| 目标命令 | 148 个 | 73 个稳定 Schema 命令、32 个兼容命令、42 个快捷命令、1 个可执行父命令 |
| Cobra 可执行路径 | 211 条 | 157 条非隐藏路径、54 条隐藏路径 |
| 可见参数名 | 110 种 | 表明相邻命令间可供 Agent 类推的参数拼写较多 |
| 原问题明细涉及命令 | 101 个 | 另有 47 个目标命令未出现在原问题明细中 |
| 有业务参数但未进入原明细 | 44 个 | 是第二轮差集审计的重点,不代表这 44 个命令全部存在问题 |
| 主要参数问题 | 15 类 | 原 7 类,加上本轮补充的 8 类 |
| 新增命令级问题明细 | 103 条 | 一行表示某个问题在一个具体命令上的表现 |
| 当前能力外场景 | 9 类 | 不能靠一对一参数名别名安全完成 |
| 补充代表性 CLI 验证 | 14 条 | 11 条失败或被保护性拦截,3 条被原有格式/拼写链路接住 |
上述“涉及命令数”存在交叉。同一个命令可能同时存在会话标识符、分页参数和时间参数问题,因此不能把各类命令数直接相加作为问题命令总数。
## 2. 分析范围与依据
本轮只分析当前产品事实,数据来源为:
1. 当前构建产生的 `dws chat ... --help`;
2. 当前 Cobra 命令树和真实 flag 定义;
3. `dws schema chat --compact -f json` 与 `internal/cli/schema_catalog.json`;
4. `skills/mono/references/products/chat.md`;
5. `internal/cli/schema_command_exclusions.json` 中记录的兼容命令和快捷命令;
6. `internal/cli/param_concepts.json`,用于判断现有参数兜底能力;
7. 必要的命令实现,用于确认参数能否在不改变值的情况下直接映射。
本轮没有把历史 `dws-eval` 实验、`merged_scan.json`、历史 badcase 或出现次数作为问题来源。补充验证用例是从当前 Cobra、Schema 和 Help 的参数差异中生成的,并且只验证预解析与 Cobra 参数解析链路,没有调用真实 RPC。
## 3. 十五类主要参数问题
| 参数问题 | 涉及命令数 | 现有兜底能力 | 处理方向 |
|---|---:|---|---|
| 会话标识符命名不统一 | 89 | 部分可以 | 拆分单值、列表和源/目标角色,按命令配置 alias/bind |
| `group` 同名异义 | 2 | 可以,但必须限定命令 | 群名与开放会话 ID 分开建 concept |
| 数字群号与开放会话 ID 混淆 | 1 | 只能拦截 | 禁止跨值域改名,需要时先查询转换 |
| 消息标识符命名、角色和单复数不统一 | 21 | 大部分可以 | 单值、列表、引用消息分别维护 |
| 用户标识符命名、角色、单复数和值域混杂 | 31 | 部分可以 | 高置信度命令级 alias,其余 block/ambiguous |
| 机器人标识符值域不同 | 4 | 不应互相兜底 | `robotCode` 与 `openBotId` 分开维护 |
| Skill 与真实命令参数不一致 | 13 | 别名表不负责 | 直接修正 Skill 文档契约 |
| 分页数量参数和游标类型不统一 | 35 | 部分可以 | 数量参数按命令映射,禁止 page/cursor 互转 |
| 搜索词参数命名不统一 | 4 | 可以 | 只在 bot find/search 精确路径配置 alias |
| 时间窗口参数名和值格式不统一 | 9 | 只能部分处理 | 一对一可映射,一对多及格式转换必须拦截 |
| 分组名称使用 `name`/`title` 不统一 | 5 | 可以 | 只在 category 命令中配置命令级 alias |
| 本地文件路径使用 `file`/`file-path` 不统一 | 2 | 可以 | 两个精确命令间做值不变映射 |
| 业务 ID 单复数与参数类型不统一 | 13 | 可保护,不应全局转换 | 拆分单值/列表 concept,默认拦截误用 |
| 参数拼写格式不统一 | 2 | 正式 CLI 已支持 | 继续使用原有格式归一并补回归测试 |
| 通用参数同名异义和值域冲突 | 6 | 别名不应处理 | 通过 Schema 类型、枚举和值域约束治理 |
## 4. 第一轮七类问题的复核结论
### 4.1 会话标识符
同一个开放会话 ID 在不同命令中使用 `group`、`id`、`chat`、`conversation-id`、`open-conversation-id` 等名称,是当前覆盖面最大的问题。
可以治理的部分是:输入值保持不变,而且在当前命令中只有一个明确目标参数。不能合并的部分包括:
- `conversation-ids` 等列表参数;
- `src-conversation-id`、`dest-conversation-id`、`source`、`target` 等方向角色;
- 数字群号 `group-id`;
- Cobra 已经原生接受的 `chat/group/id/conversation-id` 兼容参数。
### 4.2 群名、数字群号与开放会话 ID
这三者必须明确分开:
- 群名是搜索条件,需要搜索和候选消歧;
- 数字群号是 `chat group get-by-group-id` 使用的数值标识;
- 开放会话 ID 是多数群聊和消息命令直接使用的字符串标识。
别名表只能处理参数名,不能把一个群名或数字群号自动转换成开放会话 ID。
### 4.3 消息标识符
普通消息 ID、消息 ID 列表和被引用消息 ID 应分别建模。`msg-id`、`message-id`、`open-message-id` 可以在语义明确的单值命令中归一,但不能把 `msg-ids` 自动缩成单值,也不能把普通消息 ID 无条件映射为 `ref-msg-id`。
### 4.4 用户标识符
第二轮补充发现了 `sender-user-id`、`sender-open-dingtalk-id`、`at-users`、`at-user-ids` 等发送者和 @ 用户列表参数,使该类问题的涉及命令数由 28 增至 31。
需要同时保护以下边界:
- userId 与 openDingTalkId 不能跨值域改名;
- 单个用户与用户列表不能默认互转;
- 发送者、接收者、新群主、申请人、邀请人和 @ 用户不能只因都是“用户”就合并;
- 同一命令同时存在多个用户目标时,宽泛的 `user-id` 必须提示歧义。
### 4.5 机器人标识符
`robot-code` 表示 robotCode,`bot-id` 表示 openBotId。名称接近但值不通用,应分别维护 `robot_code` 和 `open_bot_id`,并阻止 `robot-id`、`bot-code` 等不明确输入跨值域归一。
### 4.6 Skill 参数契约
这类问题不是参数别名问题,应直接修改文档:
- `chat message send` 仍描述了 5 个当前命令不存在的旧文件参数;
- 2 个命令合计漏写 3 个真实参数;
- 6 个稳定命令缺少精确 Usage/Flags 段;
- 4 个 reaction 命令未说明 Cobra 原生支持的兼容会话参数。
## 5. 第二轮新增八类问题
### 5.1 分页数量和游标
35 个命令使用了 `limit`、`size`、`count`、`page` 或 `cursor`。其中:
- `limit`、`size`、`count` 在部分命令中都表示返回数量,可以逐命令审核别名;
- `page` 是页码,`cursor` 是服务端返回的翻页令牌,不能互相改名;
- `cursor` 在不同命令中存在 string、int、int64 三种类型,即使参数名相同,也不能认为取值可以跨命令复用。
### 5.2 搜索词
`chat bot find` 使用 `--query`,`chat bot search` 使用 `--name`,对应快捷命令也存在同样差异。可以在这 4 条精确路径中配置单向别名,但不能把 `name` 全局归一为 `query`,否则会影响群名、分组名和角色名等真实参数。
### 5.3 时间窗口
9 个消息查询命令混用单个 `--time` 与 `--start/--end`,并同时存在普通日期时间和 ISO-8601 格式。
单个 `start` 在业务确认后可能映射到 `time`;反方向不能把一个 `time` 无条件扩展成 `start/end`,因为结束时间和时间范围无法唯一推导,时间格式及时区也不是参数名别名能够处理的内容。
### 5.4 分组名称
普通分组创建和重命名使用 `--title`,智能分组创建使用 `--name`。两者业务语义相同、值不需要转换,可以限定在 5 个 category 主路径和快捷路径中配置命令级别名。
### 5.5 本地文件路径
`chat media upload` 使用 `--file`,`chat message send` 使用 `--file-path`。这两个参数都表示本地文件路径,可以进行精确命令的一对一映射,但不能借此恢复 Skill 中已失效的 `file-name`、`file-size`、`file-type` 等旧参数。
### 5.6 业务 ID 单复数
`category-id/category-ids`、`role-id/role-ids` 分别代表单值和列表。当前旧拼写纠错可能把单数形式自动改为复数并继续执行,但“可以解析”不等于“业务上允许扩大为批量操作”。应拆分单值/列表 concept,默认拦截,只有在精确命令确认单元素列表完全等价后才允许映射。
### 5.7 camelCase 参数
`chat chmod` 和 `chat data-auth cross-org` 暴露了 `agentCode`、`permParam`。正式 `dws` CLI 入口会经过 `RunPreParse`,已经能够把常见 kebab-case 输入归一为真实参数,不需要在 `param_concepts.json` 中重复维护。
需要记录的边界是:如果内部测试或代码绕过正式入口,直接对子 Cobra command 调用 `ParseFlags`,这一层格式归一不会生效。
### 5.8 `status`/`type` 同名异义
这两个参数在不同命令中可能表示审批动作、0/1 开关、群类型、媒体类型或资源类型。参数名虽然相同,但值域完全不同。
该问题不能靠别名表解决,也不应该建立跨命令 concept。正确方向是在 Schema 中提供真实类型、枚举和值域说明,并在执行前校验输入。
## 6. 现有兜底体系的适用边界
### 6.1 适合直接进入别名表
满足以下条件时,可以配置 concept 或命令级 alias:
1. 来源参数在当前命令中不是一个真实 flag;
2. 目标参数唯一,没有多个合理候选;
3. 输入值无需转换;
4. 参数的业务实体、角色、值域和单复数保持一致;
5. 替换后不会改变批量范围、时间范围或安全语义。
典型场景包括 bot 搜索词、category 的 `name/title`、两个本地文件路径参数,以及经过逐命令确认的分页数量参数。
### 6.2 应该 block 或 ambiguous
以下情况不应静默改名:
- 一个来源参数可能对应多个真实目标;
- 单值和列表混用;
- userId、openDingTalkId、robotCode、openBotId 等值域混用;
- 一个参数需要扩展成多个参数;
- 替换会丢失结束时间、用户角色或源/目标方向;
- 旧拼写纠错虽然能命中真实 flag,但业务语义尚未审核。
### 6.3 当前链路不能完成的九类场景
| 场景 | 当前安全处理 |
|---|---|
| 数字群号自动转换成开放会话 ID | 区分两类 ID,提示先查询再执行 |
| 群名自动转换成开放会话 ID | 保留快捷命令本地搜索;基础命令不接受群名别名 |
| 单个 userId 自动提升为用户列表 | block/ambiguous,要求明确真实目标参数 |
| Skill 的 5 个旧文件参数转换成 `file-path` | 直接修改 Skill,不做多参数猜测 |
| 强制重写已经存在的真实兼容参数 | 保留 Cobra 原生行为,只统一推荐口径 |
| 页码与游标自动互转 | 保持真实分页方式,错误输入进行保护性拦截 |
| 单个 `time` 自动扩展为 `start/end` 并转换格式 | 提示补全时间范围和正确格式 |
| 通用单数 ID 自动提升为列表 | 拆分 concept,默认 block,逐命令审核 |
| `status/type` 的值域自动翻译 | 通过 Schema 约束和执行前校验解决 |
这些场景“当前无法解决”不等于问题不处理。对于存在信息损失或误执行风险的输入,保护性拦截本身就是当前正确的处理结果。
## 7. 代表性 CLI 验证
本轮从当前参数差异中选择了 14 条代表性变体输入进行解析链路验证。
| 输入示例 | 真实参数 | 当前结果 | 判断 |
|---|---|---|---|
| `chat bot search --query robot` | `--name` | 未知参数 | 可增加命令级 alias |
| `chat bot find --name robot` | `--query` | 未知参数 | 可增加命令级 alias |
| `chat message list-favorites --limit 20` | `--size` | 未知参数 | 可逐命令审核数量别名 |
| `chat message list-all --time ...` | `--start/--end` | 未知参数 | 一对多,不应直接别名 |
| `chat message list --start ...` | `--time` | 未知参数 | 可评估精确命令的 `start -> time` |
| `chat message list-by-sender --time ...` | `--start/--end` | 保护性拦截 | 阻止信息不完整的映射 |
| `chat message list-by-sender --user-id ...` | `--sender-user-id` | 未知参数 | 发送者角色尚未纳入映射 |
| `chat category create-smart --title ...` | `--name` | 未知参数 | 可增加命令级 alias |
| `chat category create --name ...` | `--title` | 未知参数 | 可增加命令级 alias |
| `chat media upload --file-path ...` | `--file` | 未知参数 | 可增加命令级 alias |
| `chat message send-by-webhook --at-user-ids ...` | `--at-users` | 未知参数 | 可做值域明确的列表 alias |
| `chat category add-conv --category-id ...` | `--category-ids` | 旧拼写纠错通过 | 解析成功,但基数安全性仍需审核 |
| `chat group-role set-user --role-id ...` | `--role-ids` | 旧拼写纠错通过 | 解析成功,但不能推广成全局规则 |
| `chat data-auth cross-org --agent-code ...` | `--agentCode` | 格式归一通过 | 正式 CLI 入口已经支持 |
14 条用例中,11 条当前失败或被保护性拦截,3 条被原有拼写纠错或格式归一接住。这说明当前链路已经具备可复用能力,但仍需要把“偶然解析成功”与“业务语义已经审核通过”区分开。
## 8. 后续落地顺序
### 第一优先级:防止错误执行
1. 拆分数字群号、开放会话 ID 和群名称;
2. 拆分用户、消息、分类和群角色参数的单值/列表与业务角色;
3. 对时间一对多、page/cursor、跨值域和多目标输入增加 block/ambiguous;
4. 修正 Skill 中失效、漏写或缺少的参数说明。
### 第二优先级:补充高置信度兼容
1. bot 搜索词 `name/query`;
2. category 分组名称 `name/title`;
3. `file/file-path`;
4. 发送者和 @ 用户列表的精确命令级别名;
5. 逐命令审核 `limit/size/count`,不建立无边界的全局分页别名。
### 第三优先级:补强参数契约
1. 在 Schema 中完善 cursor 类型、时间格式、枚举和值域;
2. 为单值/列表和互斥参数提供显式约束;
3. 建立 Schema、Help、Skill 和 `param_concepts.json` 的产品级对账测试;
4. 将本轮 14 条代表性输入扩展为稳定回归测试集。
## 9. 复用到其他产品的分析流程
后续分析 Calendar、Drive、Contact 等产品时,可复用以下流程:
```text
盘点真实命令和可见参数
→ 对账 Schema / Help / Skill
→ 按业务实体、角色、值域和单复数归类
→ 找出异名、同名异义、格式和类型问题
→ 判断 alias / bind / block / ambiguous
→ 单列需要查值、改值、一对多和多参数转换的能力边界
→ 生成命令级问题明细与解决方案
→ 通过正式 CLI 预解析和 Cobra 参数解析验证
```
每个产品的交付物保持一致:汇报总览、参数问题明细、兜底解决方案、当前无法解决、分析依据,以及对应的文字分析报告。
## 10. 相关文件
- 详细分析工作簿:`docs/parameter-hallucination/im/im_cli_param_hallucination_analysis_20260727_v2.xlsx`
- 当前正式参数概念表:`internal/cli/param_concepts.json`
- IM 分析版参数概念表:`docs/parameter-hallucination/im/param_concepts.json`
- 稳定 Schema:`internal/cli/schema_catalog.json`
- 兼容命令范围:`internal/cli/schema_command_exclusions.json`
- Chat Skill:`skills/mono/references/products/chat.md`
- 参数归一生成:`internal/cli/param_aliases.go`
- 运行时查询:`internal/cli/param_aliases_lookup.go`
- 语义别名处理:`internal/pipeline/handlers/semantic_alias.go`
@@ -0,0 +1,322 @@
# DWS Chat / IM 参数问题分析与兜底方案
> 分析日期:2026-07-27
> 分析分支:`fix/param-hallucination`
> 分析基线:`0ef1b73`
> 分析对象:Chat / IM 产品的 CLI 参数
> 数据来源:当前 Schema、真实 `--help`、Chat Skill 和 `param_concepts.json`
> 不包含:历史实验、badcase、出现次数和模型通过率
## 1. 结论摘要
本轮共盘点148个当前可执行的 Chat / IM 命令,其中73个是稳定 Schema 命令。结论是:**Chat / IM 当前不存在大面积“Schema 参数不可执行”的基础契约问题,但存在比较明显的产品级参数语义不统一问题。**
稳定命令的 Schema 参数与真实 `--help` 参数一致,说明当前生成和发布链路基本健康。主要风险来自另外三个层面:
1. 同一个业务实体在不同命令中使用不同参数名,例如开放会话 ID 同时使用 `group`、`id`、`chat`、`conversation-id`;
2. 同一个参数名在不同命令中表示不同含义,例如 `group` 既可能是开放会话 ID,也可能是群名称关键词;
3. 用户、消息、机器人等标识符存在单复数、角色和值域差异,表面近似但不能安全互换。
这类问题不会让所有命令都失败,但会显著增加 Agent 根据相邻命令、输出字段或自然语言自行猜测参数名的概率。轻则在参数解析阶段报错,重则把错误值传入一个真实存在但含义不同的参数,形成更隐蔽的错误执行风险。
### 量化结果
| 分析维度 | 当前结果 | 说明 |
|---|---:|---|
| 可执行命令规模 | 148个 | 73个稳定 Schema 命令、32个兼容命令、42个快捷命令、1个可执行父命令 |
| 参数表面规模 | 433组命令与参数组合 | 共出现110种真实参数拼写,Agent 跨命令类推参数名的空间较大 |
| 稳定命令参数规模 | 257组命令与参数组合 | 73个稳定命令共使用85种真实参数拼写 |
| Schema 与真实 Help 一致性 | 73/73个稳定命令一致 | 当前主要矛盾不是 Schema 发布了不存在的参数,而是跨命令语义不统一 |
| 产品级主要问题 | 7类 | 覆盖异名、同名异义、值域、角色、单复数和 Skill 契约差异 |
| 会话标识符问题 | 涉及89个命令 | 是覆盖范围最大的问题,同一个开放会话 ID 使用多种参数名 |
| 用户标识符问题 | 涉及28个命令 | 同时存在 userId/openDingTalkId、单值/列表和角色差异,保护要求最高 |
| 消息标识符问题 | 涉及21个命令 | 普通单值 ID 适合归一,但列表和引用消息必须独立处理 |
| Skill 参数契约问题 | 涉及13个命令 | 包含5个失效参数、3个漏写参数、6个缺少精确说明的稳定命令,以及4个兼容参数说明缺口 |
| 当前中央兜底覆盖 | 14个 Chat 命令路径 | 已证明 alias、bind、block 等能力可用,但目前仍属于点状覆盖 |
以上问题范围存在交叉,例如同一个命令可能同时存在会话标识符问题和用户标识符问题,因此各类涉及命令数不能直接相加为问题命令总数。
聚合后共有7类主要参数问题:
| 参数问题 | 典型现象 | 现有兜底能力结论 | 推荐处理 |
|---|---|---|---|
| 会话标识符命名不统一 | 同一个开放会话 ID 使用 `group`、`id`、`chat`、`conversation-id` 等名称 | 部分可以解决 | 拆出 `open_conversation_id`,只做命令级、值不变的别名;真实兼容参数保持原生 |
| `group` 同名异义 | 有时表示开放会话 ID,有时表示群名称关键词 | 可以按命令解决 | 建立独立 `group_name`,只在两个快捷命令中配置 `group-name -> group` |
| 数字群号与开放会话 ID 混淆 | `group-id` 既容易被理解为数字群号,也容易被理解为开放会话 ID | 不能靠改名自动转换 | 拆分概念并阻止错误映射;需要时先查询再执行 |
| 消息标识符命名、角色和单复数不统一 | `msg-id`、`message-id`、`open-message-id`、`msg-ids`、`ref-msg-id` 并存 | 大部分可以解决 | 单值、列表和引用角色分别建 concept,禁止单复数互转 |
| 用户标识符命名、角色、单复数和值域混杂 | `user/users` 可能表示 userId、openDingTalkId、混合列表或角色型用户 | 只能部分解决 | 安全的单值命令增加别名;列表、混合值域和多目标命令使用 block/ambiguous |
| 机器人标识符值域不同 | `robot-code` 与 `bot-id` 名称相近但不是同一种值 | 不应互相兜底 | 分开维护 `robot_code` 和 `open_bot_id`,禁止跨值域别名 |
| Skill 与真实命令参数不一致 | Skill 存在已失效参数、漏写参数和缺少命令段 | 别名表不负责解决 | 直接修正 Skill,并保留真实 Help/Schema 为参数事实 |
因此,本轮不需要为了统一表面命名去改每个 `skill.go`。当前中央兜底链路适合处理“名称不同但业务含义和值完全相同”的命令级别名;需要查询、改值、改变单复数或选择多个可能目标的情况,必须保护性拦截或继续由命令本地逻辑处理。
完整命令和参数明细见同目录输出的 Excel 工作簿。
## 2. 分析依据
本轮只使用当前产品事实:
1. 当前构建的 `dws chat ... --help`;
2. `dws schema chat --compact -f json` 和 `internal/cli/schema_catalog.json`;
3. `skills/mono/references/products/chat.md`;
4. `internal/cli/schema_command_exclusions.json` 中的兼容命令和快捷命令;
5. `internal/cli/param_concepts.json`,用于判断现有兜底能力;
6. 必要的命令实现,用于确认参数值是否可以原样传递。
没有使用 `dws-eval`、`merged_scan.json`、历史 alias 工作簿或任何实验次数。
## 3. 参数问题与解决方式
### 3.1 会话标识符命名不统一
#### 问题
同一个开放会话 ID 在不同命令中使用了以下真实参数名:
```text
group
id
chat
conversation-id
open-conversation-id
src-conversation-id
dest-conversation-id
source
target
conversation-ids
```
该问题涉及89个命令,是 Chat / IM 中覆盖面最大的参数问题。Agent 在一个命令中学会 `--conversation-id` 后,很容易在另一个只接受 `--group` 或 `--id` 的命令中继续使用它。
#### 需要保留的边界
- `conversation-ids` 是列表,不能和单值概念混用;
- `src-`、`dest-`、`source`、`target` 表示方向角色,不能全部无条件简化;
- reaction 相关命令已经原生接受 `chat/group/id/conversation-id`,中央层不应再次重写这些真实参数。
#### 解决方案
1. 将当前含义过宽的 `group_id` 拆分或收敛为 `open_conversation_id`;
2. 另建 `open_conversation_ids`;
3. 对 `--id`、`--group` 等宽泛真实参数,通过精确命令 `bind` 说明它代表开放会话 ID;
4. 对当前命令不存在、但语义和值完全相同的参数,增加命令级别名;
5. 对真实兼容参数保持原生解析,只在 Skill 中推荐一个主要参数名。
结论:现有兜底功能可以解决其中的“一对一名称差异”,但不能把单值变成列表,也不应消除源/目标角色。
### 3.2 `group` 同名异义
#### 问题
大多数基础命令中的 `--group` 表示开放会话 ID,但以下两个快捷命令中的 `--group` 表示群名称搜索关键词:
```text
chat +group-members --group
chat +send-to-group --group
```
如果把 `group` 全局归入开放会话 ID,快捷命令会被错误治理;如果把它全局理解为群名称,又会破坏大量基础命令。
#### 解决方案
- 建立独立 `group_name` concept;
- 保留 `chat +group-members` 已有的 `group-name -> group` 命令级别名;
- 为 `chat +send-to-group` 增加相同的命令级别名;
- 基础命令不接收群名称别名。
结论:现有兜底功能能够解决,但必须严格限制到精确命令。
### 3.3 数字群号与开放会话 ID 混淆
#### 问题
```text
chat group get-by-group-id --group-id
```
这里的 `group-id` 是数字群号;其他大量 Chat 命令需要的是字符串形式的开放会话 ID。两者不是同一个值,只是可以通过查询建立转换关系。
#### 解决方案
- 将 `numeric_group_id` 与 `open_conversation_id` 完全分开;
- 从开放会话 ID concept 中排除数字群号;
- 当用户提供数字群号但目标命令需要开放会话 ID 时,提示先执行 `get-by-group-id`;
- 不配置全局 `group-id -> group/conversation-id`。
结论:现有兜底功能可以阻止错误改名,但不能完成“查询后替换参数值”。
### 3.4 消息标识符命名、角色和单复数不统一
#### 问题
同一个普通消息 ID 使用:
```text
msg-id
message-id
open-message-id
```
列表使用 `msg-ids`,引用消息使用 `ref-msg-id`。合计涉及21个命令。
#### 解决方案
- 新增 `open_message_id`,只覆盖单个普通消息 ID;
- 新增 `open_message_ids`,只覆盖消息 ID 列表;
- `ref-msg-id` 保持“被引用消息”的角色,可单独建立 `referenced_open_message_id`;
- 在单值命令中 block `message-ids/msg-ids` 等列表拼写;
- 明确排除 `open-task-id`、`topic-id`、`resource-id` 等其他实体。
结论:大部分名称差异可以通过现有 concept 和命令级别名解决,单复数不能自动互转。
### 3.5 用户标识符命名、角色、单复数和值域混杂
#### 问题
用户参数同时存在以下差异:
- `--user` 可能是单个 userId,也可能承载逗号分隔列表;
- `--users` 可能是 userId 列表、openDingTalkId 列表或两者混合;
- `receiver`、`new-owner`、`ref-sender`、`applicant` 等参数保留了业务角色,但名字没有写明标识符类型;
- 同一个命令有时同时存在 `user` 和 `users`,错误的 `user-id` 可能对应多个合理目标。
该问题涉及28个命令,是最需要保护性约束的一类。
#### 可以先做的安全别名
以下命令的 `--user` 明确是单个 userId,可以审核 `user-id -> user`:
```text
chat conversation-info
chat group transfer-owner
chat group-role query-user
chat group-role remove-user
chat group-role set-user
chat message list
chat message list-direct
chat message send
chat +conversation-info
chat +messages-list-direct
```
#### 必须保护的情况
- 同一命令同时存在 `user` 和 `users`:对宽泛来源参数提示歧义;
- 真实目标是列表:不把单数 `user-id` 静默提升为列表;
- `users` 接受混合 userId/openDingTalkId:不归入单一 `user_ids`;
- 角色型参数:只允许同角色、同值域的命令级别名。
结论:现有兜底功能可以处理高置信度单值别名,也可以通过 block/ambiguous 防止错误执行,但不能安全地自动完成单值到列表或不同身份值域之间的转换。
### 3.6 机器人标识符值域不同
Chat 中至少存在:
```text
robot-code # robotCode
bot-id # openBotId
```
Skill 还会提到机器人开放用户标识。它们的名称相近,但值不能互换。
解决方案是分别维护 `robot_code` 和 `open_bot_id`,保留各自真实参数;禁止 `robot-id`、`bot-code` 等不明确的跨值域映射。
结论:这里的主要目标是防止错误兜底,不是增加更多自动别名。
### 3.7 Skill 与真实命令参数不一致
#### Skill 中存在、当前命令不存在
`chat message send` 的 Skill 仍列出:
```text
dentry-id
space-id
file-name
file-type
file-size
```
当前真实命令使用 `file-path` 完成本地文件上传和发送。旧参数集合不能通过一对一别名转换成 `file-path`。
#### Skill 漏写真参数
| 命令 | 漏写参数 |
|---|---|
| `chat group create` | `thread`、`type` |
| `chat group list-my-groups` | `exclude-muted` |
#### Skill 缺少精确命令参数段
```text
chat category list
chat group set-admin
chat group-mute
chat group-mute-member
chat message list-direct
chat set-top
```
四个 reaction 命令还存在真实兼容参数 `chat/group/id` 未在 Skill 中说明的问题。这些参数本身可以正常执行,Skill 应继续推荐 `conversation-id`,并在兼容说明中注明其他名称是原生命令支持的兼容参数。
结论:这类问题直接修改 Skill;`param_concepts.json` 不应该承担修复文档错误的责任。
## 4. 第一轮建议改造
### 优先级一:先避免错误执行
1. 将数字群号与开放会话 ID 从当前 `group_id` 边界中拆开;
2. 将群名称与开放会话 ID 分开;
3. 对用户单值/列表、多目标和混合值域配置 block 或 ambiguous;
4. 修正 Skill 中已不存在的参数和漏写参数。
### 优先级二:增加高置信度兼容
1. 新增 `open_message_id` 和 `open_message_ids`;
2. 对10个明确的单值 userId 命令审核 `user-id -> user`;
3. 为 `chat +send-to-group` 增加 `group-name -> group`;
4. 按命令逐步扩展 `open_conversation_id`,只接受值不变的一对一映射。
### 优先级三:收敛公开说明
1. Skill 为每类标识符推荐一个主要参数名;
2. 原生命令已经支持的兼容参数放在兼容说明,不由中央链路重复重写;
3. 建立产品级 Schema、Help、Skill 参数对账检查。
## 5. 当前能力支持不了或不应该做的事项
| 场景 | 原因 | 当前安全处理 |
|---|---|---|
| 数字群号自动变成开放会话 ID | 需要跨命令查询,参数值会变化 | 明确区分两种 ID,引导先查询 |
| 群名自动变成开放会话 ID | 需要搜索并处理零个、一个或多个候选 | 只保留两个快捷命令的本地搜索能力 |
| 单个 userId 自动变成用户列表 | 单复数和作用范围发生变化,同命令可能有多个目标 | block/ambiguous,引导使用真实列表参数 |
| Skill 的5个旧文件参数自动变成 `file-path` | 多个旧字段不能一对一生成本地文件路径和上传流程 | 修改 Skill,使用当前 `file-path` |
| 强制把已经存在的真实兼容参数统一改名 | 会改变 Cobra 的原生解析、参数冲突和脚本兼容行为 | 保持原生,只统一推荐口径 |
这些限制不阻塞第一轮 concept 治理。第一轮可以先完成安全别名、保护性拦截和文档修复。
## 6. 复用到其他产品的流程
后续分析 Calendar、Drive、Contact 等产品时,复用同一流程:
```text
盘点当前命令和真实参数
→ 对账 Schema / Help / Skill
→ 按业务实体归并参数
→ 找出异名、同名异义、单复数、角色和值域问题
→ 判断 concept / 命令级 alias / bind / block / ambiguous
→ 单列当前链路支持不了的查询、改值和多参数转换
→ 通过最终参数组装和原生解析测试验证
```
每个产品最终只需要维护同样的五个汇报页面:汇报总览、参数问题明细、兜底解决方案、当前无法解决、分析依据。
## 7. 相关文件
- 稳定 Schema:`internal/cli/schema_catalog.json`
- 兼容命令范围:`internal/cli/schema_command_exclusions.json`
- Chat Skill:`skills/mono/references/products/chat.md`
- 参数概念源:`internal/cli/param_concepts.json`
- 参数归一生成:`internal/cli/param_aliases.go`
- 运行时查询:`internal/cli/param_aliases_lookup.go`
- 语义别名处理:`internal/pipeline/handlers/semantic_alias.go`
@@ -0,0 +1,399 @@
{
"$schema": "./param_concepts.schema.json",
"version": 1,
"morphological_rules": {
"kebab_camel_equivalence": {"desc": "--page-size == --pageSize", "enabled": true},
"separator_normalization": {"desc": "-, _, . are equivalent separators", "enabled": true},
"trailing_id_tolerance": {"desc": "--base tolerates --base-id when only one is a real flag on the command", "enabled": true, "guard": "the two must not both be real flags with different semantics"},
"pluralization": {"desc": "--id<->--ids, --user<->--users", "enabled": false, "reason": "singular/list semantics can differ; handled by concept+intersection or command override instead"}
},
"concepts": {
"search_query": {"denotes": "search keyword string", "canonical_hint": "query", "members": ["query", "keyword", "keywords", "q", "search-word"], "excludes": ["name", "subject", "text", "title"], "commands": ["aitable +base-search", "contact +dept-members", "contact +resolve-dept", "contact +search-user", "doc +find-doc", "doc +search", "doc +template-search", "mail +find-mail-user", "mail user search", "oa +search-forms", "oa approval search-forms"], "risk": "green"},
"pagination_size": {"denotes": "returned item count upper bound", "canonical_hint": "limit", "members": ["limit", "size", "page-size", "max-results", "max-result", "take", "top", "per-page"], "excludes": ["count", "page", "cursor"], "commands": ["aitable record query", "calendar event list", "chat message list", "devdoc article search", "doc +comment-list", "doc +find-doc", "doc +list", "doc +search", "doc +template-list", "doc +template-search", "doc +version-list", "mail thread list", "oa +list-executed"], "risk": "green"},
"page_number": {"denotes": "one-based page number", "canonical_hint": "page", "members": ["page", "page-no", "current-page", "page-num"], "excludes": ["cursor", "page-index", "page-size", "page-token"], "commands": ["devdoc article search"], "risk": "green"},
"page_cursor": {"denotes": "pagination cursor/token", "canonical_hint": "cursor", "members": ["cursor", "next-cursor", "page-token", "next-token", "next-page-token"], "excludes": ["page", "offset"], "commands": ["calendar event list", "doc +comment-list", "doc +list", "doc +search", "doc +template-list", "doc +template-search", "doc +version-list"], "risk": "green"},
"content_text": {"denotes": "text body content", "canonical_hint": "text", "members": ["text", "content", "body"], "excludes": ["title", "name"], "commands": ["doc +comment-create", "doc +comment-reply", "doc +doc-append", "doc block insert", "doc block update", "doc comment create", "doc comment create-inline", "doc comment reply", "doc comment update"], "risk": "green"},
"time_start": {"denotes": "start time point with unchanged value format and unit", "canonical_hint": "start", "members": ["start", "start-time", "start-date", "from", "from-date", "begin", "since", "time-min", "min-time"], "excludes": ["date", "time", "end"], "commands": ["calendar event list", "chat message list-all", "report list"], "risk": "yellow"},
"time_end": {"denotes": "end time point with unchanged value format and unit", "canonical_hint": "end", "members": ["end", "end-time", "end-date", "time-max", "max-time"], "excludes": ["date", "time", "start"], "commands": ["calendar event list"], "risk": "yellow"},
"base_id": {"denotes": "multi-dimensional table Base id", "canonical_hint": "base-id", "members": ["base", "base-id", "base-token"], "excludes": [], "commands": ["aitable +field-get", "aitable +list-tables", "aitable +record-query", "aitable +record-share-url", "aitable +table-get"], "risk": "green"},
"dept_id": {"denotes": "single department id", "canonical_hint": "dept", "members": ["dept", "dept-id", "department", "department-id", "parent", "parent-id"], "excludes": ["depts", "dept-ids", "department-ids", "name", "query"], "commands": ["contact +list-sub-depts", "contact dept list-children"], "risk": "yellow"},
"dept_ids": {"denotes": "department id list", "canonical_hint": "dept-ids", "members": ["depts", "dept-ids", "department-ids"], "excludes": ["dept", "dept-id", "department-id", "name", "query"], "commands": ["contact +list-dept-members"], "risk": "yellow"},
"group_id": {"denotes": "single DingTalk numeric groupId", "canonical_hint": "group-id", "members": ["group-id"], "excludes": ["group", "conversation-id", "chat", "chat-id", "open-conversation-id", "conversation-ids", "open-conversation-ids", "group-name", "name", "id"], "commands": ["chat +chat-get-by-id", "chat group get-by-group-id"], "risk": "yellow"},
"open_conversation_id": {"denotes": "single DingTalk openConversationId with unchanged value", "canonical_hint": "conversation-id", "members": ["group", "conversation-id", "chat", "chat-id", "open-conversation-id"], "excludes": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids", "group-name", "name", "id", "source", "target", "src-conversation-id", "dest-conversation-id"], "commands": ["chat +category-add-conversation", "chat +category-remove-conversation", "chat +chat-add-bot", "chat +chat-audit-join", "chat +chat-bots", "chat +chat-dismiss", "chat +chat-invite-url", "chat +chat-members-get", "chat +chat-mute", "chat +chat-mute-member", "chat +chat-quit", "chat +chat-remove-bot", "chat +chat-role-add", "chat +chat-role-list", "chat +chat-role-query-user", "chat +chat-role-remove", "chat +chat-role-remove-user", "chat +chat-role-set-user", "chat +chat-role-update", "chat +chat-set-admin", "chat +chat-set-history", "chat +chat-transfer-owner", "chat +chat-update-alias", "chat +chat-update-icon", "chat +chat-update-nick", "chat +chat-update-settings", "chat +conversation-clear-messages", "chat +conversation-clear-red-point", "chat +conversation-hide", "chat +conversation-info", "chat +conversation-mark-read", "chat +conversation-mark-unread", "chat +conversation-mute", "chat +flag-cancel", "chat +flag-create", "chat +messages-add-emoji", "chat +messages-add-text-emotion", "chat +messages-list-pin", "chat +messages-read-status", "chat +messages-recall-by-bot", "chat +messages-remove-emoji", "chat +messages-remove-text-emotion", "chat +messages-reply", "chat +messages-resource-download", "chat +messages-resource-url", "chat +messages-send-by-bot", "chat +messages-set-pin", "chat +messages-set-top", "chat +messages-unset-pin", "chat +messages-unset-top", "chat category add-conv", "chat category remove-conv", "chat chmod", "chat clear-messages", "chat clear-red-point", "chat conversation-info", "chat group audit-join-validation", "chat group bots", "chat group dismiss", "chat group invite-url", "chat group members", "chat group members add", "chat group members add-bot", "chat group members list-by-ids", "chat group members remove", "chat group members remove-bot", "chat group notice create", "chat group notice edit", "chat group notice get", "chat group notice list", "chat group quit", "chat group rename", "chat group set-admin", "chat group set-history", "chat group transfer-owner", "chat group update-alias", "chat group update-icon", "chat group update-nick", "chat group update-settings", "chat group-mute", "chat group-mute-member", "chat group-role add", "chat group-role list", "chat group-role query-user", "chat group-role remove", "chat group-role remove-user", "chat group-role set-user", "chat group-role update", "chat hide", "chat mark-read", "chat mark-unread", "chat message add-favorite", "chat message download-media", "chat message list", "chat message list-mentions", "chat message list-pin-msg", "chat message list-topic-replies", "chat message read-status", "chat message recall", "chat message recall-by-bot", "chat message remove-favorite", "chat message reply", "chat message search", "chat message send", "chat message send-by-bot", "chat message send-card", "chat message set-pin-msg", "chat message set-top-msg", "chat message unset-pin-msg", "chat message unset-top-msg", "chat mute-at-all", "chat mute-red-envelope", "chat set-top"], "risk": "yellow"},
"open_conversation_ids": {"denotes": "DingTalk openConversationId list with unchanged element values", "canonical_hint": "conversation-ids", "members": ["conversation-ids", "open-conversation-ids", "groups"], "excludes": ["group-id", "group-ids", "conversation-id", "open-conversation-id", "chat-id"], "commands": ["chat message search-advanced"], "risk": "yellow"},
"group_name": {"denotes": "group-name search keyword, not a group identifier", "canonical_hint": "group-name", "members": ["group-name"], "excludes": ["group-id", "conversation-id", "open-conversation-id", "chat-id", "id"], "commands": ["chat +group-members", "chat +send-to-group"], "risk": "yellow"},
"open_message_id": {"denotes": "single DingTalk openMessageId with unchanged value", "canonical_hint": "open-message-id", "members": ["msg-id", "message-id", "open-message-id"], "excludes": ["msg-ids", "message-ids", "open-message-ids", "ref-msg-id", "src-msg-id", "open-task-id", "topic-id", "resource-id"], "commands": ["chat +conversation-mark-read", "chat +flag-cancel", "chat +flag-create", "chat +messages-add-emoji", "chat +messages-add-text-emotion", "chat +messages-forward", "chat +messages-read-status", "chat +messages-remove-emoji", "chat +messages-remove-text-emotion", "chat +messages-resource-download", "chat +messages-set-pin", "chat +messages-set-top", "chat +messages-unset-pin", "chat +messages-unset-top", "chat mark-read", "chat message add-emoji", "chat message add-favorite", "chat message add-text-emotion", "chat message download-media", "chat message forward", "chat message read-status", "chat message recall", "chat message remove-emoji", "chat message remove-favorite", "chat message remove-text-emotion", "chat message set-pin-msg", "chat message set-top-msg", "chat message unset-pin-msg", "chat message unset-top-msg"], "risk": "yellow"},
"open_message_ids": {"denotes": "DingTalk openMessageId list with unchanged element values", "canonical_hint": "msg-ids", "members": ["msg-ids", "message-ids", "open-message-ids"], "excludes": ["msg-id", "message-id", "open-message-id", "ref-msg-id", "src-msg-id"], "commands": ["chat +flag-cancel", "chat +flag-create", "chat +messages-combine-forward", "chat +messages-mget", "chat message combine-forward", "chat message list-by-ids", "chat message list-emotion-replies"], "risk": "yellow"},
"referenced_open_message_id": {"denotes": "referenced DingTalk openMessageId in a reply", "canonical_hint": "ref-msg-id", "members": ["ref-msg-id", "ref-message-id"], "excludes": ["msg-id", "message-id", "open-message-id", "msg-ids", "src-msg-id"], "commands": ["chat +messages-reply", "chat message reply"], "risk": "yellow"},
"user_id": {"denotes": "single user id", "canonical_hint": "user-id", "members": ["user", "user-id", "userid", "uid", "staff-id"], "excludes": ["at-user-ids", "to-user", "users", "user-ids", "name"], "commands": ["chat +chat-role-query-user", "chat +chat-role-set-user", "chat +messages-list-direct", "chat chmod", "chat conversation-info", "chat group transfer-owner", "chat group-role query-user", "chat group-role remove-user", "chat group-role set-user", "chat message list", "chat message send", "contact user profile get"], "risk": "yellow"},
"user_ids": {"denotes": "user id list", "canonical_hint": "user-ids", "members": ["users", "user-ids"], "excludes": ["user", "user-id", "userid", "uid", "staff-id", "at-user-ids"], "commands": ["attendance +check-result", "attendance check result", "chat +messages-batch-send-by-bot", "chat group members remove", "chat group set-admin", "chat group-mute-member", "chat message read-status", "chat message search-advanced", "chat message send-by-bot"], "risk": "yellow"},
"open_dingtalk_ids": {"denotes": "DingTalk openDingTalkId list with unchanged element values", "canonical_hint": "open-dingtalk-ids", "members": ["open-dingtalk-ids"], "excludes": ["user", "user-id", "user-ids", "staff-id", "users"], "commands": ["chat +chat-members-get", "chat category create-smart", "chat group members list-by-ids", "chat message send-by-bot"], "risk": "yellow"},
"ding_id": {"denotes": "DING id", "canonical_hint": "ding-id", "members": ["ding-id", "open-ding-id"], "excludes": ["id"], "commands": ["ding message receiver-status"], "risk": "yellow"},
"folder_id": {"denotes": "drive folder id", "canonical_hint": "folder", "members": ["folder", "folder-id"], "excludes": ["space-id"], "commands": ["drive list", "mail folder update"], "risk": "green"},
"space_id": {"denotes": "drive/wiki/Doc workspace id with unchanged value", "canonical_hint": "space-id", "members": ["space-id", "space", "workspace", "workspace-id"], "excludes": ["folder", "node"], "commands": ["doc +copy", "doc +list", "doc +move", "drive info"], "risk": "yellow"},
"app_id": {"denotes": "application id", "canonical_hint": "unified-app-id", "members": ["app-id", "unified-app-id", "application-id"], "excludes": ["app-key", "app-secret", "agent-id"], "commands": ["dev app get"], "risk": "yellow"},
"robot_code": {"denotes": "robot code", "canonical_hint": "robot-code", "members": ["robot-code", "robot"], "excludes": ["robot-id", "bot-id", "open-bot-id", "bot-code"], "commands": ["chat +chat-add-bot", "chat +messages-batch-recall-by-bot", "chat +messages-batch-send-by-bot", "chat +messages-recall-by-bot", "chat +messages-send-by-bot", "chat group members add-bot", "chat message recall-by-bot", "chat message send-by-bot", "ding message send"], "risk": "yellow"},
"open_bot_id": {"denotes": "single DingTalk openBotId with unchanged value", "canonical_hint": "bot-id", "members": ["bot-id", "open-bot-id"], "excludes": ["robot-code", "robot", "robot-id", "bot-code"], "commands": ["chat +chat-remove-bot", "chat group members remove-bot"], "risk": "yellow"},
"doc_node_id": {"denotes": "single DingTalk document nodeId or accepted document URL/token with unchanged value", "canonical_hint": "node", "members": ["node", "node-id", "doc", "doc-id", "file-id", "document-id", "url"], "excludes": ["id", "folder", "folder-id", "parent-id", "workspace", "workspace-id", "block-id", "comment-id", "comment-key", "job-id", "task-id", "template-id", "version", "revision"], "commands": ["doc +comment-create", "doc +comment-list", "doc +comment-reply", "doc +copy", "doc +doc-append", "doc +export-submit", "doc +move", "doc +version-list", "doc +version-revert", "doc +version-save"], "risk": "yellow"},
"doc_comment_key": {"denotes": "single DingTalk document commentKey with unchanged value", "canonical_hint": "comment-key", "members": ["comment-key", "comment-id"], "excludes": ["id", "node", "node-id", "doc-id", "block-id"], "commands": ["doc +comment-reply", "doc comment delete", "doc comment reply", "doc comment update"], "risk": "yellow"},
"doc_version_number": {"denotes": "single DingTalk document historical version number with unchanged integer value", "canonical_hint": "version", "members": ["version", "version-number", "version-no"], "excludes": ["revision", "id", "node", "node-id", "doc-id"], "commands": ["doc +version-revert", "doc version revert"], "risk": "yellow"}
},
"command_overrides": {
"chat group rename": {"bind": {"id": "open_conversation_id"}, "note": "This command's real --id carries one openConversationId; aliases reduce to --id without changing the value."},
"chat group members": {"bind": {"id": "open_conversation_id"}},
"chat group members add": {"bind": {"id": "open_conversation_id"}, "block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a list and may contain mixed userId/openDingTalkId values; singular inputs are not promoted automatically."},
"chat group members remove": {"bind": {"id": "open_conversation_id"}},
"chat message add-emoji": {"scoped_aliases": {"chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat message add-text-emotion": {"scoped_aliases": {"chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat message remove-emoji": {"scoped_aliases": {"chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat message remove-text-emotion": {"scoped_aliases": {"chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id stay native; numeric groupId and list spellings are rejected."},
"chat mute": {"scoped_aliases": {"group": "conversation-id", "chat-id": "conversation-id", "open-conversation-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --conversation-id/--id/--chat remain unchanged; other reviewed openConversationId spellings reduce to --conversation-id."},
"drive list": {"ambiguous": ["space"], "note": "both --space-id and native compatibility --workspace-id/--workspace exist; bare --space cannot choose one"},
"drive upload": {"ambiguous": ["space"], "note": "both --space-id and native compatibility --workspace-id/--workspace exist; bare --space cannot choose one"},
"ding +receiver-status": {"scoped_aliases": {"id": "ding-id"}, "note": "generic id reduces to ding-id"},
"ding message receiver-status": {"scoped_aliases": {"id": "ding-id"}},
"contact user profile get": {"scoped_aliases": {"id": "staff-id", "ids": "staff-id"}, "note": "user-id is reduced by the user_id concept; generic id/ids bound explicitly"},
"mail folder update": {"bind": {"id": "folder_id"}, "note": "this command's --id is the folder id; --folder-id reduces to --id"},
"mail message search": {"scoped_aliases": {"subject": "query"}, "scope_strict": true, "note": "never globalize: mail template create has a real and different --subject"},
"calendar event list": {"scoped_aliases": {"date": "start"}, "note": "reviewed against ParseISOTimeToMillis and final list_calendar_events payload; --date is normalized centrally while the command's existing hidden compatibility flags remain native fallbacks"},
"chat +bot-find": {"scoped_aliases": {"name": "query"}, "scope_strict": true, "note": "On this exact bot-search shortcut, --name and --query denote the same search keyword; --name must not become a global search alias."},
"chat +chat-messages": {"scoped_aliases": {"chat": "group"}, "scope_strict": true, "note": "Evaluation compatibility: --chat carries one stable openConversationId and normalizes to the existing --group identifier route."},
"chat +search-msg": {"scoped_aliases": {"chat": "group"}, "scope_strict": true, "note": "Evaluation compatibility: scalar --chat carries one stable openConversationId and normalizes to the existing scalar --group filter."},
"chat bot find": {"scoped_aliases": {"name": "query"}, "scope_strict": true, "note": "On this exact bot-search command, --name and --query denote the same search keyword; --name must not become a global search alias."},
"chat +bot-search": {"scoped_aliases": {"query": "name", "current-page": "page"}, "block": ["cursor"], "scope_strict": true, "note": "Only the reviewed bot keyword and page-number spellings are accepted; cursor pagination cannot be converted to a page number."},
"chat bot search": {"scoped_aliases": {"query": "name", "current-page": "page"}, "block": ["cursor"], "scope_strict": true, "note": "Only the reviewed bot keyword and page-number spellings are accepted; cursor pagination cannot be converted to a page number."},
"chat message list-favorites": {"scoped_aliases": {"limit": "size"}, "scope_strict": true, "note": "On this exact command, both names denote the same bounded result count; the numeric value is unchanged."},
"chat +messages-list-unread-conversations": {"scoped_aliases": {"limit": "count", "size": "count"}, "scope_strict": true, "note": "On this exact command, limit and size both denote the returned unread-conversation count."},
"chat +unread-chats": {"scoped_aliases": {"limit": "count", "size": "count"}, "scope_strict": true, "note": "On this exact command, limit and size both denote the returned unread-conversation count."},
"chat message list-unread-conversations": {"scoped_aliases": {"limit": "count", "size": "count"}, "scope_strict": true, "note": "On this exact command, limit and size both denote the returned unread-conversation count."},
"chat +messages-list-direct": {"scoped_aliases": {"start": "time"}, "block": ["end"], "scope_strict": true, "note": "This exact command accepts one start boundary in yyyy-MM-dd HH:mm:ss; an end-only input cannot be represented."},
"chat message list": {"scoped_aliases": {"start": "time"}, "block": ["end"], "scope_strict": true, "note": "This exact command accepts one start boundary in yyyy-MM-dd HH:mm:ss; an end-only input cannot be represented."},
"chat message list-by-sender": {"scoped_aliases": {"user-id": "sender-user-id", "open-dingtalk-id": "sender-open-dingtalk-id"}, "block": ["time"], "scope_strict": true, "note": "Only same-role sender identifiers are mapped; --time cannot supply the required RFC3339 start/end range."},
"contact +resolve-dept": {"bind": {"name": "search_query"}, "note": "The real --name is a department-name search keyword and carries the search_query concept on this shortcut."},
"contact +list-sub-depts": {"block": ["name", "query"], "note": "--dept is an integer department id; names and search queries require a separate resolution command"},
"contact +dept-members": {"bind": {"dept": "search_query"}, "scoped_aliases": {"name": "dept"}, "note": "The real --dept is a department-name search keyword; search spellings come from search_query, while --name remains command-scoped."},
"chat message send": {"scoped_aliases": {"to-user": "user", "file": "file-path"}, "note": "Recipient and local-file-path aliases are exact to this command; obsolete file metadata flags remain unsupported."},
"chat +group-members": {"bind": {"group": "group_name"}, "note": "The real --group is a group-name search keyword on this shortcut, not an identifier."},
"chat +category-create": {"scoped_aliases": {"name": "title"}, "scope_strict": true, "note": "The reviewed name/title mapping preserves the category display-name value on this exact shortcut."},
"chat category create": {"scoped_aliases": {"name": "title"}, "scope_strict": true, "note": "The reviewed name/title mapping preserves the category display-name value on this exact command."},
"chat +category-rename": {"scoped_aliases": {"name": "title"}, "block": ["category-ids"], "scope_strict": true, "note": "The display-name alias is exact; a category-id list is not accepted where one category is required."},
"chat category rename": {"scoped_aliases": {"name": "title"}, "block": ["category-ids"], "scope_strict": true, "note": "The display-name alias is exact; a category-id list is not accepted where one category is required."},
"chat +category-delete": {"block": ["category-ids"], "note": "This command requires one category id; list cardinality is not reduced automatically."},
"chat category delete": {"block": ["category-ids"], "note": "This command requires one category id; list cardinality is not reduced automatically."},
"chat category list-conversations": {"block": ["category-ids"], "note": "This command requires one category id; list cardinality is not reduced automatically."},
"chat category add-conv": {"block": ["category-id"], "note": "This command requires a category-id list; one id is not promoted into a batch input."},
"chat category remove-conv": {"block": ["category-id"], "note": "This command requires a category-id list; one id is not promoted into a batch input."},
"chat +chat-role-update": {"block": ["role-ids"], "note": "This command requires one role id; list cardinality is not reduced automatically."},
"chat group-role remove": {"block": ["role-ids"], "note": "This command requires one role id; list cardinality is not reduced automatically."},
"chat group-role update": {"block": ["role-ids"], "note": "This command requires one role id; list cardinality is not reduced automatically."},
"chat +chat-role-set-user": {"block": ["role-id"], "note": "This command requires a role-id list; one id is not promoted into a batch input."},
"chat group-role remove-user": {"block": ["role-id"], "note": "This command requires a role-id list; one id is not promoted into a batch input."},
"chat group-role set-user": {"block": ["role-id"], "note": "This command requires a role-id list; one id is not promoted into a batch input."},
"chat +messages-send-by-webhook": {"scoped_aliases": {"at-user-ids": "at-users"}, "scope_strict": true, "note": "Both names denote the same userId list used for @ mentions on this exact shortcut."},
"chat message send-by-webhook": {"scoped_aliases": {"at-user-ids": "at-users"}, "scope_strict": true, "note": "Both names denote the same userId list used for @ mentions on this exact command."},
"doc block insert": {"block": ["before-block-id"], "note": "Parent and reference roles remain distinct. --before-block-id needs both --ref-block and --where before, while role-free --block-id cannot choose parent versus reference.", "scoped_aliases": {"parent-block-id": "parent-block", "ref-block-id": "ref-block", "reference-block-id": "ref-block"}, "ambiguous": ["block-id"], "scope_strict": true},
"chat message send-by-bot": {"scoped_aliases": {"at-users": "at-user-ids"}, "block": ["user-id", "to-user-id"], "ambiguous": ["at-ids"], "note": "The reviewed @ userId-list alias is exact; singular recipients are not promoted, and bare --at-ids cannot choose an identifier domain."},
"doc +export-get": {"block": ["doc-id", "document-id", "file-id", "node", "node-id", "task-id", "url"], "note": "This command queries one export jobId. Document node identifiers and import taskId spellings are different entities and are rejected.", "scoped_aliases": {"export-job-id": "job-id"}, "scope_strict": true},
"doc block delete": {"block": ["index"], "note": "index (position) vs node (node id) are different"},
"doc +copy": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder"}, "block": ["parent-id"], "scope_strict": true, "note": "These exact shortcuts require a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings reduce to --folder; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."},
"doc +list": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder"}, "block": ["parent-id"], "scope_strict": true, "note": "These exact shortcuts require a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings reduce to --folder; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."},
"doc +move": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder"}, "block": ["parent-id"], "scope_strict": true, "note": "These exact shortcuts require a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings reduce to --folder; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."},
"doc comment create": {"scoped_aliases": {"mentioned-open-conversation-ids": "mentioned-open-conversation-id", "open-conversation-id": "mentioned-open-conversation-id", "open-conversation-ids": "mentioned-open-conversation-id"}, "block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids"], "scope_strict": true, "note": "The target is a list of mentioned openConversationIds. Explicit open-conversation spellings preserve the list value; numeric groupId and role-free conversation spellings are rejected."},
"doc comment reply": {"scoped_aliases": {"mentioned-open-conversation-ids": "mentioned-open-conversation-id", "open-conversation-id": "mentioned-open-conversation-id", "open-conversation-ids": "mentioned-open-conversation-id"}, "block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids"], "scope_strict": true, "note": "The target is a list of mentioned openConversationIds. Explicit open-conversation spellings preserve the list value; numeric groupId and role-free conversation spellings are rejected."},
"doc comment update": {"scoped_aliases": {"mentioned-open-conversation-ids": "mentioned-open-conversation-id", "open-conversation-id": "mentioned-open-conversation-id", "open-conversation-ids": "mentioned-open-conversation-id"}, "block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids"], "scope_strict": true, "note": "The target is a list of mentioned openConversationIds. Explicit open-conversation spellings preserve the list value; numeric groupId and role-free conversation spellings are rejected."},
"doc +comment-create": {"block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids", "mentioned-open-conversation-id", "mentioned-open-conversation-ids", "open-conversation-id", "open-conversation-ids"], "note": "This shortcut has no group-mention flag or payload field. Group-conversation spellings are blocked instead of inventing unsupported capability; route to the stable doc comment command when group mentions are required."},
"doc +comment-reply": {"block": ["chat-id", "chat-ids", "conversation-id", "conversation-ids", "group-id", "group-ids", "mentioned-open-conversation-id", "mentioned-open-conversation-ids", "open-conversation-id", "open-conversation-ids"], "note": "This shortcut has no group-mention flag or payload field. Group-conversation spellings are blocked instead of inventing unsupported capability; route to the stable doc comment command when group mentions are required."},
"doc media insert": {"scoped_aliases": {"ref-block-id": "ref-block", "reference-block-id": "ref-block"}, "block": ["before-block-id", "parent-block", "parent-block-id"], "ambiguous": ["block-id"], "scope_strict": true, "note": "Media insertion supports a reference block but no parent-block role. --before-block-id additionally needs --where before; role-free --block-id is left ambiguous."},
"doc read": {"block": ["before-block-id", "parent-block-id", "ref-block-id", "reference-block-id"], "ambiguous": ["block-id"], "note": "A section read requires --scope section plus a start/end boundary. A role-free --block-id cannot be reduced to one flag without inventing the missing scope/boundary role."},
"doc export get": {"scoped_aliases": {"export-job-id": "job-id"}, "block": ["doc-id", "document-id", "file-id", "node", "node-id", "task-id", "url"], "scope_strict": true, "note": "This command queries one export jobId. Document node identifiers and import taskId spellings are different entities and are rejected."},
"doc import get": {"scoped_aliases": {"import-task-id": "task-id"}, "block": ["doc-id", "document-id", "file-id", "job-id", "node", "node-id", "url"], "scope_strict": true, "note": "This command queries one import taskId. Document node identifiers and export jobId spellings are different entities and are rejected."},
"doc +share-doc": {"block": ["doc", "doc-id", "document-id", "file-id", "id", "node", "node-id"], "note": "The real --url requires a shareable document link. Name-only normalization cannot turn a document nodeId into a URL, so identifier spellings are rejected with guidance to provide --url."},
"doc update": {"block": ["version", "version-no", "version-number"], "note": "--revision is an optimistic-concurrency revision, not a historical document version number. Version spellings must not reduce to --revision."},
"report outbox list": {"block": ["template-type"], "note": "type vs name are different fields"},
"chat group members add-bot": {"bind": {"id": "open_conversation_id"}},
"chat group members list-by-ids": {"bind": {"id": "open_conversation_id", "users": "open_dingtalk_ids"}, "block": ["user-id", "user-ids"], "note": "This command's --id carries openConversationId, while --users carries an openDingTalkId list."},
"chat group members remove-bot": {"bind": {"id": "open_conversation_id"}},
"chat +send-to-group": {"bind": {"group": "group_name"}, "note": "The real --group is a group-name search keyword on this shortcut, not an identifier."},
"chat group share-invite": {"scoped_aliases": {"source-conversation-id": "source", "target-conversation-id": "target"}, "block": ["group-id", "group-ids", "user", "user-id", "userid", "uid", "staff-id"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "note": "A role-free conversation identifier cannot choose between source and target; --receiver requires openDingTalkId and must not accept userId spellings."},
"chat message combine-forward": {"scoped_aliases": {"src-open-cid": "src-conversation-id", "dest-open-cid": "dest-conversation-id", "source-conversation-id": "src-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id"}, "block": ["group-id", "group-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "note": "Source and destination conversation roles are preserved; a role-free identifier is ambiguous."},
"chat message forward": {"scoped_aliases": {"src-open-cid": "src-conversation-id", "dest-open-cid": "dest-conversation-id", "source-conversation-id": "src-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id"}, "block": ["group-id", "group-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "note": "Source and destination conversation roles are preserved; a role-free identifier is ambiguous."},
"chat message forward-topic": {"scoped_aliases": {"src-open-conversation-id": "src-conversation-id", "dest-open-conversation-id": "dest-conversation-id", "source-conversation-id": "src-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id", "src-open-message-id": "src-msg-id", "source-message-id": "src-msg-id"}, "block": ["group-id", "group-ids", "msg-id", "message-id", "open-message-id"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "note": "Conversation and message source/destination roles are preserved; role-free identifiers are rejected."},
"chat +conversation-info": {"block": ["user", "user-id", "userid", "uid", "staff-id"], "note": "This shortcut accepts --open-dingtalk-id, not userId; use stable chat conversation-info when userId resolution is needed."},
"chat group create": {"block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a list and may contain mixed identifier domains."},
"chat +chat-set-admin": {"block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a mixed userId/openDingTalkId list."},
"chat +messages-read-status": {"block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a mixed userId/openDingTalkId list."},
"chat category create-smart": {"bind": {"members": "open_dingtalk_ids"}, "scoped_aliases": {"title": "name"}, "note": "The real --members is an openDingTalkId list; the reviewed title/name alias is exact to the category display name."},
"chat group audit-join-validation": {"ambiguous": ["user", "user-id", "userid", "uid", "staff-id"], "note": "A role-free user identifier cannot choose between the required --applicant and --inviter roles."},
"chat message reply": {"block": ["user", "user-id", "userid", "uid", "staff-id"], "note": "The required --ref-sender is a role-specific openDingTalkId and must not accept generic userId spellings."},
"chat message send-card": {"block": ["user", "user-id", "userid", "uid", "staff-id"], "note": "The real --receiver is a role-specific openDingTalkId and must not accept generic userId spellings."},
"chat +category-add-conversation": {"block": ["category-id"], "note": "The real --category-ids is a list; a singular category ID is not promoted automatically."},
"chat +category-list-conversations": {"block": ["category-ids"], "note": "The real --category-id is singular; list cardinality is not reduced automatically."},
"chat +category-remove-conversation": {"block": ["category-id"], "note": "The real --category-ids is a list; a singular category ID is not promoted automatically."},
"chat +chat-add-bot": {"bind": {"id": "open_conversation_id"}, "note": "The real --id is the group's openConversationId; robotCode and openBotId remain different domains."},
"chat +chat-audit-join": {"scoped_aliases": {"applicant-user-id": "applicant", "inviter-user-id": "inviter"}, "ambiguous": ["user", "user-id", "userid", "uid", "staff-id"], "scope_strict": true, "note": "A role-free user identifier cannot choose between applicant and inviter."},
"chat +chat-create": {"block": ["user-id", "open-dingtalk-id"], "note": "The real --users field is a list and may contain mixed identifier domains; a singular value is not promoted."},
"chat +chat-get-by-id": {"block": ["group", "conversation-id", "chat", "chat-id", "open-conversation-id", "conversation-ids", "open-conversation-ids", "group-name", "name", "id"], "note": "The real --group-id is numeric groupId; no CID or group-name spelling can be value-preservingly converted."},
"chat +chat-members-get": {"bind": {"id": "open_conversation_id", "users": "open_dingtalk_ids"}, "block": ["group", "group-name", "user-id", "user-ids"], "note": "The real --id is openConversationId and --users is an openDingTalkId list. The observed --group spelling carried a natural group name and is blocked; explicit CID spellings and --chat remain value-preserving aliases."},
"chat +chat-members-list": {"scoped_aliases": {"chat-id": "conversation-id", "id": "conversation-id"}, "block": ["query", "keyword", "group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "scope_strict": true, "note": "Native --chat/--open-conversation-id stay native; member filtering by query is unsupported and group-name resolution stays on --group/--chat-query."},
"chat +chat-mute-member": {"scoped_aliases": {"user-ids": "users", "open-dingtalk-ids": "users"}, "block": ["user", "user-id", "open-dingtalk-id"], "scope_strict": true, "note": "The target accepts a mixed identifier list; list spellings preserve values, but singular inputs are not promoted."},
"chat +chat-remove-bot": {"bind": {"id": "open_conversation_id"}, "note": "The real --id is openConversationId; --bot-id is separately governed by open_bot_id."},
"chat +chat-role-remove": {"block": ["role-ids"], "note": "The command removes one role ID; list cardinality is not reduced."},
"chat +chat-role-remove-user": {"scoped_aliases": {"user-id": "user", "open-dingtalk-id": "user"}, "block": ["role-id"], "scope_strict": true, "note": "The single --user accepts either identifier domain; --role-ids remains a list."},
"chat +chat-transfer-owner": {"scoped_aliases": {"user-id": "new-owner", "open-dingtalk-id": "new-owner"}, "scope_strict": true, "note": "The only user role is the new owner, and the target accepts either userId or openDingTalkId without changing the value."},
"chat +chat-update": {"scoped_aliases": {"conversation-id": "group", "open-conversation-id": "group", "chat-id": "group", "title": "name", "new-title": "name"}, "block": ["id", "group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "scope_strict": true, "note": "--group accepts a name or CID, so only explicit CID spellings are mapped; generic --id is blocked."},
"chat +conversation-set-top": {"scoped_aliases": {"open-conversation-id": "conversation-id", "chat-id": "conversation-id", "open-conversation-ids": "conversation-ids", "chat-ids": "conversation-ids"}, "block": ["group", "groups", "group-id", "group-ids", "top", "set-top"], "scope_strict": true, "note": "Singular/list cardinality stays explicit; top/set-top cannot be rewritten to the inverse --off switch."},
"chat +feed-group-query-item": {"scoped_aliases": {"open-conversation-ids": "conversation-ids", "chat-ids": "conversation-ids"}, "block": ["group", "groups", "group-id", "group-ids", "conversation-id", "open-conversation-id"], "scope_strict": true, "note": "The real field is an openConversationId list; group names and singular IDs are not converted."},
"chat +flag-list": {"scoped_aliases": {"limit": "size"}, "block": ["max", "max-results", "max-size", "count", "page", "page-size", "per-page"], "scope_strict": true, "note": "Only limit and size are reviewed as the same page bound; total-count and page-number spellings are not equivalent."},
"chat +messages-batch-recall-by-bot": {"block": ["msg-id", "message-id", "open-message-id", "msg-ids", "message-ids", "open-message-ids"], "note": "--keys carries processQueryKey values returned by bot sending; it is not an openMessageId field."},
"chat +messages-combine-forward": {"scoped_aliases": {"src-open-cid": "src-conversation-id", "src-open-conversation-id": "src-conversation-id", "source-conversation-id": "src-conversation-id", "dest-open-cid": "dest-conversation-id", "dest-open-conversation-id": "dest-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id"}, "block": ["group-id", "group-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "scope_strict": true, "note": "Source and destination conversation roles remain explicit; role-free CID spellings cannot choose a side."},
"chat +messages-forward": {"scoped_aliases": {"src-open-cid": "src-conversation-id", "src-open-conversation-id": "src-conversation-id", "source-conversation-id": "src-conversation-id", "dest-open-cid": "dest-conversation-id", "dest-open-conversation-id": "dest-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id", "src-open-message-id": "msg-id", "source-message-id": "msg-id"}, "block": ["group-id", "group-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "scope_strict": true, "note": "The message role is uniquely the source message, but source/destination conversation roles cannot be inferred from a generic CID."},
"chat +messages-forward-topic": {"scoped_aliases": {"src-open-conversation-id": "src-conversation-id", "source-conversation-id": "src-conversation-id", "dest-open-conversation-id": "dest-conversation-id", "target-conversation-id": "dest-conversation-id", "destination-conversation-id": "dest-conversation-id", "src-open-message-id": "src-msg-id", "source-message-id": "src-msg-id"}, "block": ["group-id", "group-ids", "msg-id", "message-id", "open-message-id", "msg-ids", "message-ids", "open-message-ids"], "ambiguous": ["conversation-id", "open-conversation-id", "group", "chat", "chat-id", "id"], "scope_strict": true, "note": "Source and destination conversation roles and the source-message role remain explicit; role-free message IDs and list cardinality are not inferred."},
"chat +messages-list": {"scoped_aliases": {"start": "time"}, "block": ["before", "before-time", "end", "direction", "page-all", "count", "max", "max-results", "max-size", "page-size"], "scope_strict": true, "note": "start preserves the same boundary value; before/direction require multi-parameter or value transforms and page-all requires iteration. Native --conversation-id/--id/--size remain native."},
"chat +messages-recall-by-bot": {"block": ["msg-id", "message-id", "open-message-id", "msg-ids", "message-ids", "open-message-ids"], "note": "--keys carries processQueryKey values, not openMessageId values."},
"chat +messages-reply": {"scoped_aliases": {"msg-id": "ref-msg-id", "open-message-id": "ref-msg-id"}, "block": ["group", "msg-ids", "message-ids", "open-message-ids"], "scope_strict": true, "note": "The observed --group spelling carried a natural group name and is blocked. The only message role is the referenced message; plural IDs are not accepted, while --chat remains a CID alias and native --message-id stays native."},
"chat +messages-resource-download": {"block": ["download-dir"], "note": "--output may be a file or directory under workspace safety rules; a download directory cannot be assumed equivalent."}
},
"validation_fixture": {
"cases": [
{"command": "oa +search-forms", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 28},
{"command": "aitable +list-tables", "emitted": "base-id", "expect": "base", "via": "concept:base_id+morph", "occ": 26},
{"command": "mail +find-mail-user", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 18},
{"command": "aitable +field-get", "emitted": "base", "expect": "base-id", "via": "concept:base_id+morph", "occ": 8},
{"command": "aitable +record-query", "emitted": "base", "expect": "base-id", "via": "concept:base_id+morph", "occ": 8},
{"command": "aitable +table-get", "emitted": "base", "expect": "base-id", "via": "concept:base_id+morph", "occ": 8},
{"command": "doc block update", "emitted": "content", "expect": "text", "via": "concept:content_text", "occ": 6},
{"command": "contact +resolve-dept", "emitted": "query", "expect": "name", "via": "concept:search_query+bind", "occ": 4},
{"command": "devdoc article search", "emitted": "limit", "expect": "size", "via": "concept:pagination_size", "occ": 2},
{"command": "devdoc article search", "emitted": "page-size", "expect": "size", "via": "concept:pagination_size", "occ": 2},
{"command": "devdoc article search", "emitted": "current-page", "expect": "page", "via": "concept:page_number", "occ": 2},
{"command": "mail message search", "emitted": "subject", "expect": "query", "via": "override:scoped_strict", "occ": 4},
{"command": "aitable +record-share-url", "emitted": "base", "expect": "base-id", "via": "concept:base_id+morph", "occ": 3},
{"command": "aitable record query", "emitted": "max-results", "expect": "limit", "via": "concept:pagination_size", "occ": 2},
{"command": "calendar event list", "emitted": "date", "expect": "start", "via": "override:scoped(reviewed+payload)", "occ": 2},
{"command": "calendar event list", "emitted": "start-time", "expect": "start", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "min-time", "expect": "start", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "time-min", "expect": "start", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "end-time", "expect": "end", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "time-max", "expect": "end", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "max-results", "expect": "limit", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "next-cursor", "expect": "cursor", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "calendar event list", "emitted": "calendar", "expect": "calendar-id", "via": "native:reviewed-compatibility-fallback", "occ": 2},
{"command": "chat message list", "emitted": "max-results", "expect": "limit", "via": "concept:pagination_size", "occ": 2},
{"command": "chat message list-by-sender", "emitted": "time", "expect": "did-you-mean:blocked", "via": "guard:time-format-boundary", "occ": 2},
{"command": "doc +template-search", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 2},
{"command": "doc block insert", "emitted": "content", "expect": "text", "via": "concept:content_text", "occ": 2},
{"command": "drive list", "emitted": "folder-id", "expect": "folder", "via": "concept:folder_id+morph", "occ": 2},
{"command": "mail thread list", "emitted": "max-results", "expect": "limit", "via": "concept:pagination_size", "occ": 2},
{"command": "mail user search", "emitted": "query", "expect": "keyword", "via": "concept:search_query", "occ": 2},
{"command": "oa +list-executed", "emitted": "take", "expect": "limit", "via": "concept:pagination_size", "occ": 2},
{"command": "oa approval search-forms", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 2},
{"command": "report list", "emitted": "from-date", "expect": "start", "via": "concept:time_start", "occ": 2},
{"command": "aitable +base-search", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 1},
{"command": "contact +search-user", "emitted": "keyword", "expect": "query", "via": "concept:search_query", "occ": 1},
{"command": "chat group rename", "emitted": "group", "expect": "id", "via": "override:bind(open_conversation_id)", "occ": 31},
{"command": "ding +receiver-status", "emitted": "id", "expect": "ding-id", "via": "override:scoped(ding_id)", "occ": 24},
{"command": "chat group members", "emitted": "group", "expect": "id", "via": "override:bind(open_conversation_id)", "occ": 8},
{"command": "contact +list-sub-depts", "emitted": "dept-id", "expect": "dept", "via": "concept:dept_id+morph", "occ": 4},
{"command": "contact +list-sub-depts", "emitted": "name", "expect": "did-you-mean:blocked", "via": "guard:name-vs-id", "occ": 2},
{"command": "contact +list-sub-depts", "emitted": "query", "expect": "did-you-mean:blocked", "via": "guard:query-vs-id", "occ": 2},
{"command": "contact user profile get", "emitted": "user-id", "expect": "staff-id", "via": "concept:user_id", "occ": 2},
{"command": "contact user profile get", "emitted": "id", "expect": "staff-id", "via": "override:scoped", "occ": 2},
{"command": "contact user profile get", "emitted": "ids", "expect": "staff-id", "via": "override:scoped", "occ": 2},
{"command": "chat message send-by-bot", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list", "occ": 4},
{"command": "chat message send-by-bot", "emitted": "to-user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list", "occ": 1},
{"command": "dev app get", "emitted": "app-id", "expect": "unified-app-id", "via": "concept:app_id", "occ": 5},
{"command": "chat message list-all", "emitted": "from", "expect": "start", "via": "concept:time_start", "occ": 2},
{"command": "chat message list-all", "emitted": "start-time", "expect": "start", "via": "concept:time_start", "occ": 2},
{"command": "chat message search-advanced", "emitted": "group", "expect": "conversation-ids", "via": "native:reviewed-single-to-list", "occ": 4},
{"command": "chat message send", "emitted": "to-user", "expect": "user", "via": "override:scoped(reviewed+payload)", "occ": 4},
{"command": "contact +dept-members", "emitted": "name", "expect": "dept", "via": "override:scoped(reviewed)", "occ": 2},
{"command": "contact +dept-members", "emitted": "query", "expect": "dept", "via": "concept:search_query+bind", "occ": 2},
{"command": "contact dept list-children", "emitted": "parent-id", "expect": "dept", "via": "concept:dept_id", "occ": 2},
{"command": "contact dept list-children", "emitted": "parent", "expect": "dept", "via": "concept:dept_id", "occ": 2},
{"command": "ding message receiver-status", "emitted": "id", "expect": "ding-id", "via": "override:scoped(ding_id)", "occ": 2},
{"command": "ding message receiver-status", "emitted": "open-ding-id", "expect": "ding-id", "via": "concept:ding_id", "occ": 2},
{"command": "chat group members add", "emitted": "group", "expect": "id", "via": "override:bind(open_conversation_id)", "occ": 3},
{"command": "attendance +check-result", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "concept:user_ids+exclude", "occ": 2},
{"command": "attendance check result", "emitted": "user-ids", "expect": "users", "via": "concept:user_ids", "occ": 2},
{"command": "chat group members remove", "emitted": "group", "expect": "id", "via": "override:bind(open_conversation_id)", "occ": 2},
{"command": "chat group set-admin", "emitted": "user-id", "expect": "user", "via": "native:reviewed-compatibility-alias", "occ": 2},
{"command": "ding message send", "emitted": "robot", "expect": "robot-code", "via": "concept:robot_code", "occ": 2},
{"command": "doc +export-get", "emitted": "node", "expect": "did-you-mean:blocked", "via": "override:block", "occ": 2},
{"command": "doc block delete", "emitted": "index", "expect": "did-you-mean:blocked", "via": "override:block", "occ": 2},
{"command": "doc block insert", "emitted": "before-block-id", "expect": "did-you-mean:blocked", "via": "guard:requires-multi-parameter-transform", "occ": 2},
{"command": "drive info", "emitted": "workspace", "expect": "space-id", "via": "concept:space_id", "occ": 2},
{"command": "mail folder update", "emitted": "folder-id", "expect": "id", "via": "override:bind(folder_id)", "occ": 2},
{"command": "report outbox list", "emitted": "template-type", "expect": "did-you-mean:blocked", "via": "override:block", "occ": 2},
{"command": "chat +group-members", "emitted": "group-name", "expect": "group", "via": "concept:group_name+bind"},
{"command": "chat group get-by-group-id", "emitted": "conversation-id", "expect": "did-you-mean:blocked", "via": "guard:open-conversation-id-vs-group-id"},
{"command": "chat group get-by-group-id", "emitted": "id", "expect": "did-you-mean:blocked", "via": "guard:generic-id-vs-group-id"},
{"command": "chat group rename", "emitted": "conversation-id", "expect": "id", "via": "concept:open_conversation_id+bind"},
{"command": "chat group rename", "emitted": "group-id", "expect": "did-you-mean:blocked", "via": "guard:group-id-vs-open-conversation-id"},
{"command": "chat message send", "emitted": "conversation-id", "expect": "group", "via": "concept:open_conversation_id"},
{"command": "chat message add-emoji", "emitted": "open-conversation-id", "expect": "conversation-id", "via": "override:scoped"},
{"command": "chat message add-emoji", "emitted": "group-id", "expect": "did-you-mean:blocked", "via": "guard:group-id-vs-open-conversation-id"},
{"command": "chat +group-members", "emitted": "conversation-id", "expect": "did-you-mean:blocked", "via": "guard:group-name-vs-open-conversation-id"},
{"command": "chat +send-to-group", "emitted": "group-name", "expect": "group", "via": "concept:group_name+bind"},
{"command": "chat message search-advanced", "emitted": "open-conversation-ids", "expect": "conversation-ids", "via": "concept:open_conversation_ids"},
{"command": "chat message search-advanced", "emitted": "group-ids", "expect": "did-you-mean:blocked", "via": "guard:group-id-list-vs-open-conversation-id-list"},
{"command": "chat message recall", "emitted": "message-id", "expect": "msg-id", "via": "concept:open_message_id"},
{"command": "chat message add-favorite", "emitted": "msg-id", "expect": "open-message-id", "via": "concept:open_message_id"},
{"command": "chat message list-by-ids", "emitted": "message-ids", "expect": "msg-ids", "via": "concept:open_message_ids"},
{"command": "chat message list-by-ids", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat message reply", "emitted": "ref-message-id", "expect": "ref-msg-id", "via": "concept:referenced_open_message_id"},
{"command": "chat message reply", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:message-role"},
{"command": "chat message forward-topic", "emitted": "src-open-message-id", "expect": "src-msg-id", "via": "override:scoped-role"},
{"command": "chat message forward-topic", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:message-role"},
{"command": "chat message combine-forward", "emitted": "src-open-cid", "expect": "src-conversation-id", "via": "override:scoped-role"},
{"command": "chat message forward-topic", "emitted": "dest-open-conversation-id", "expect": "dest-conversation-id", "via": "override:scoped-role"},
{"command": "chat message forward", "emitted": "conversation-id", "expect": "did-you-mean:ambiguous", "via": "guard:source-vs-destination-role"},
{"command": "chat group share-invite", "emitted": "conversation-id", "expect": "did-you-mean:ambiguous", "via": "guard:source-vs-target-role"},
{"command": "chat message send", "emitted": "user-id", "expect": "user", "via": "concept:user_id"},
{"command": "chat message list", "emitted": "user-id", "expect": "user", "via": "concept:user_id"},
{"command": "chat +messages-list-direct", "emitted": "user-id", "expect": "user", "via": "concept:user_id"},
{"command": "attendance +check-result", "emitted": "user-ids", "expect": "users", "via": "concept:user_ids"},
{"command": "contact +list-sub-depts", "emitted": "parent-id", "expect": "dept", "via": "concept:dept_id"},
{"command": "chat +conversation-info", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:user-id-vs-open-dingtalk-id"},
{"command": "chat group members remove", "emitted": "user-ids", "expect": "users", "via": "concept:user_ids"},
{"command": "chat group members remove", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat group create", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +chat-set-admin", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +messages-read-status", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat group members list-by-ids", "emitted": "open-dingtalk-ids", "expect": "users", "via": "concept:open_dingtalk_ids+bind"},
{"command": "chat group members remove-bot", "emitted": "open-bot-id", "expect": "bot-id", "via": "concept:open_bot_id"},
{"command": "chat group members remove-bot", "emitted": "robot-code", "expect": "did-you-mean:blocked", "via": "guard:robot-code-vs-open-bot-id"},
{"command": "chat group members add-bot", "emitted": "robot", "expect": "robot-code", "via": "concept:robot_code"},
{"command": "chat +bot-find", "emitted": "name", "expect": "query", "via": "override:scoped"},
{"command": "chat bot find", "emitted": "name", "expect": "query", "via": "override:scoped"},
{"command": "chat +bot-search", "emitted": "query", "expect": "name", "via": "override:scoped"},
{"command": "chat bot search", "emitted": "query", "expect": "name", "via": "override:scoped"},
{"command": "chat message list-favorites", "emitted": "limit", "expect": "size", "via": "override:scoped"},
{"command": "chat bot search", "emitted": "current-page", "expect": "page", "via": "override:scoped"},
{"command": "chat bot search", "emitted": "cursor", "expect": "did-you-mean:blocked", "via": "guard:page-number-vs-cursor"},
{"command": "chat message list-unread-conversations", "emitted": "limit", "expect": "count", "via": "override:scoped"},
{"command": "chat +messages-list-unread-conversations", "emitted": "size", "expect": "count", "via": "override:scoped"},
{"command": "chat message list", "emitted": "start", "expect": "time", "via": "override:scoped"},
{"command": "chat message list", "emitted": "end", "expect": "did-you-mean:blocked", "via": "guard:single-time-vs-range"},
{"command": "chat message list-all", "emitted": "time", "expect": "did-you-mean:blocked", "via": "guard:time-range-required"},
{"command": "chat message list-by-sender", "emitted": "user-id", "expect": "sender-user-id", "via": "override:scoped-role"},
{"command": "chat message list-by-sender", "emitted": "open-dingtalk-id", "expect": "sender-open-dingtalk-id", "via": "override:scoped-role"},
{"command": "chat category create-smart", "emitted": "title", "expect": "name", "via": "override:scoped"},
{"command": "chat category create", "emitted": "name", "expect": "title", "via": "override:scoped"},
{"command": "chat message send", "emitted": "file", "expect": "file-path", "via": "override:scoped"},
{"command": "chat category add-conv", "emitted": "category-id", "expect": "did-you-mean:blocked", "via": "override:block-cardinality"},
{"command": "chat category rename", "emitted": "category-ids", "expect": "did-you-mean:blocked", "via": "override:block-cardinality"},
{"command": "chat group-role set-user", "emitted": "role-id", "expect": "did-you-mean:blocked", "via": "override:block-cardinality"},
{"command": "chat group-role update", "emitted": "role-ids", "expect": "did-you-mean:blocked", "via": "override:block-cardinality"},
{"command": "chat message send-by-webhook", "emitted": "at-user-ids", "expect": "at-users", "via": "override:scoped-role"},
{"command": "chat message send-by-bot", "emitted": "at-users", "expect": "at-user-ids", "via": "override:scoped-role"},
{"command": "chat message send-by-bot", "emitted": "at-ids", "expect": "did-you-mean:ambiguous", "via": "guard:user-id-vs-open-dingtalk-id"},
{"command": "chat +bot-search", "emitted": "current-page", "expect": "page", "via": "override:scoped"},
{"command": "chat +category-create", "emitted": "name", "expect": "title", "via": "override:scoped"},
{"command": "chat +category-rename", "emitted": "name", "expect": "title", "via": "override:scoped"},
{"command": "chat +messages-list-direct", "emitted": "start", "expect": "time", "via": "override:scoped"},
{"command": "chat +messages-list-unread-conversations", "emitted": "limit", "expect": "count", "via": "override:scoped"},
{"command": "chat +messages-send-by-webhook", "emitted": "at-user-ids", "expect": "at-users", "via": "override:scoped-role"},
{"command": "chat +unread-chats", "emitted": "limit", "expect": "count", "via": "override:scoped"},
{"command": "chat +unread-chats", "emitted": "size", "expect": "count", "via": "override:scoped"},
{"command": "chat category rename", "emitted": "name", "expect": "title", "via": "override:scoped"},
{"command": "chat message list-unread-conversations", "emitted": "size", "expect": "count", "via": "override:scoped"},
{"command": "doc +comment-create", "emitted": "node-id", "expect": "node", "via": "concept:doc_node_id"},
{"command": "doc +doc-append", "emitted": "node", "expect": "doc", "via": "concept:doc_node_id"},
{"command": "doc +find-doc", "emitted": "keyword", "expect": "query", "via": "concept:search_query"},
{"command": "doc +search", "emitted": "q", "expect": "query", "via": "concept:search_query"},
{"command": "doc +comment-list", "emitted": "max-results", "expect": "limit", "via": "concept:pagination_size"},
{"command": "doc +list", "emitted": "page-token", "expect": "cursor", "via": "concept:page_cursor"},
{"command": "doc +copy", "emitted": "workspace-id", "expect": "workspace", "via": "concept:space_id"},
{"command": "doc +copy", "emitted": "parent-folder-id", "expect": "folder", "via": "override:scoped-doc-folder"},
{"command": "doc +copy", "emitted": "parent-id", "expect": "did-you-mean:blocked", "via": "guard:doc-folder-value-domain"},
{"command": "doc +comment-reply", "emitted": "comment-id", "expect": "comment-key", "via": "concept:doc_comment_key"},
{"command": "doc comment reply", "emitted": "mentioned-open-conversation-ids", "expect": "mentioned-open-conversation-id", "via": "override:scoped-role-list"},
{"command": "doc comment reply", "emitted": "group-id", "expect": "did-you-mean:blocked", "via": "guard:numeric-group-id-vs-open-conversation-id"},
{"command": "doc +comment-create", "emitted": "mentioned-open-conversation-id", "expect": "did-you-mean:blocked", "via": "guard:shortcut-missing-capability"},
{"command": "doc block insert", "emitted": "parent-block-id", "expect": "parent-block", "via": "override:scoped-block-role"},
{"command": "doc block insert", "emitted": "block-id", "expect": "did-you-mean:ambiguous", "via": "guard:parent-vs-reference-block-role"},
{"command": "doc media insert", "emitted": "before-block-id", "expect": "did-you-mean:blocked", "via": "guard:requires-ref-block-plus-where"},
{"command": "doc read", "emitted": "block-id", "expect": "did-you-mean:ambiguous", "via": "guard:requires-scope-and-boundary-role"},
{"command": "doc export get", "emitted": "node", "expect": "did-you-mean:blocked", "via": "guard:document-node-vs-export-job"},
{"command": "doc import get", "emitted": "job-id", "expect": "did-you-mean:blocked", "via": "guard:export-job-vs-import-task"},
{"command": "doc +version-revert", "emitted": "version-number", "expect": "version", "via": "concept:doc_version_number"},
{"command": "doc +version-revert", "emitted": "revision", "expect": "did-you-mean:blocked", "via": "concept:doc_version_number+exclude"},
{"command": "doc update", "emitted": "version", "expect": "did-you-mean:blocked", "via": "guard:historical-version-vs-edit-revision"},
{"command": "doc +share-doc", "emitted": "node-id", "expect": "did-you-mean:blocked", "via": "guard:node-id-needs-url-conversion"},
{"command": "doc +comment-create", "emitted": "body", "expect": "content", "via": "concept:content_text"},
{"command": "doc +doc-append", "emitted": "content", "expect": "text", "via": "concept:content_text"},
{"command": "doc +export-submit", "emitted": "doc-id", "expect": "node", "via": "concept:doc_node_id"},
{"command": "doc +move", "emitted": "parent-folder-id", "expect": "folder", "via": "override:scoped-doc-folder"},
{"command": "doc +template-list", "emitted": "next-token", "expect": "cursor", "via": "concept:page_cursor"},
{"command": "doc +version-list", "emitted": "page-size", "expect": "limit", "via": "concept:pagination_size"},
{"command": "doc +version-save", "emitted": "file-id", "expect": "node", "via": "concept:doc_node_id"},
{"command": "doc comment create", "emitted": "text", "expect": "content", "via": "concept:content_text"},
{"command": "doc comment create-inline", "emitted": "body", "expect": "content", "via": "concept:content_text"},
{"command": "doc comment delete", "emitted": "comment-id", "expect": "comment-key", "via": "concept:doc_comment_key"},
{"command": "doc comment update", "emitted": "comment-id", "expect": "comment-key", "via": "concept:doc_comment_key"},
{"command": "doc version revert", "emitted": "version-no", "expect": "version", "via": "concept:doc_version_number"},
{"command": "chat +chat-messages", "emitted": "chat", "expect": "group", "via": "override:scoped-eval"},
{"command": "chat +search-msg", "emitted": "chat", "expect": "group", "via": "override:scoped-eval"},
{"command": "chat +chat-update", "emitted": "chat-id", "expect": "group", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-update", "emitted": "conversation-id", "expect": "group", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-update", "emitted": "open-conversation-id", "expect": "group", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-update", "emitted": "id", "expect": "did-you-mean:blocked", "via": "guard:generic-id-value-domain"},
{"command": "chat +chat-update", "emitted": "title", "expect": "name", "via": "override:scoped-group-title"},
{"command": "chat +chat-update", "emitted": "new-title", "expect": "name", "via": "override:scoped-group-title"},
{"command": "chat +flag-list", "emitted": "limit", "expect": "size", "via": "override:scoped-page-bound"},
{"command": "chat +flag-list", "emitted": "max", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +flag-list", "emitted": "max-results", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +flag-list", "emitted": "max-size", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +chat-members-list", "emitted": "chat-id", "expect": "conversation-id", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-members-list", "emitted": "id", "expect": "conversation-id", "via": "override:scoped-explicit-cid"},
{"command": "chat +chat-members-list", "emitted": "query", "expect": "did-you-mean:blocked", "via": "guard:unsupported-member-filter"},
{"command": "chat +conversation-set-top", "emitted": "open-conversation-id", "expect": "conversation-id", "via": "override:scoped-explicit-cid"},
{"command": "chat +conversation-set-top", "emitted": "chat-ids", "expect": "conversation-ids", "via": "override:scoped-explicit-cid-list"},
{"command": "chat +conversation-set-top", "emitted": "groups", "expect": "did-you-mean:blocked", "via": "guard:group-name-or-list-ambiguity"},
{"command": "chat +conversation-set-top", "emitted": "top", "expect": "did-you-mean:blocked", "via": "guard:inverse-boolean-semantics"},
{"command": "chat +chat-members-get", "emitted": "conversation-id", "expect": "id", "via": "concept:open_conversation_id+bind"},
{"command": "chat +chat-members-get", "emitted": "open-dingtalk-ids", "expect": "users", "via": "concept:open_dingtalk_ids+bind"},
{"command": "chat +chat-members-get", "emitted": "group", "expect": "did-you-mean:blocked", "via": "guard:group-name-vs-open-conversation-id"},
{"command": "chat +chat-members-get", "emitted": "chat", "expect": "id", "via": "concept:open_conversation_id+bind"},
{"command": "chat +chat-get-by-id", "emitted": "group", "expect": "did-you-mean:blocked", "via": "guard:open-conversation-id-vs-numeric-group-id"},
{"command": "chat +messages-list", "emitted": "start", "expect": "time", "via": "override:scoped-time-boundary"},
{"command": "chat +messages-list", "emitted": "count", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +messages-list", "emitted": "max-results", "expect": "did-you-mean:blocked", "via": "guard:page-size-vs-total-count"},
{"command": "chat +messages-list", "emitted": "page-all", "expect": "did-you-mean:blocked", "via": "guard:requires-pagination-loop"},
{"command": "chat +messages-reply", "emitted": "msg-id", "expect": "ref-msg-id", "via": "override:scoped-reference-message"},
{"command": "chat +messages-reply", "emitted": "group", "expect": "did-you-mean:blocked", "via": "guard:group-name-vs-open-conversation-id"},
{"command": "chat +messages-reply", "emitted": "chat", "expect": "conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +flag-cancel", "emitted": "group", "expect": "conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +flag-cancel", "emitted": "chat", "expect": "conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +flag-create", "emitted": "group", "expect": "conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +chat-add-bot", "emitted": "conversation-id", "expect": "id", "via": "concept:open_conversation_id+bind"},
{"command": "chat +chat-add-bot", "emitted": "robot", "expect": "robot-code", "via": "concept:robot_code"},
{"command": "chat +chat-audit-join", "emitted": "applicant-user-id", "expect": "applicant", "via": "override:scoped-user-role"},
{"command": "chat +chat-audit-join", "emitted": "user-id", "expect": "did-you-mean:ambiguous", "via": "guard:applicant-vs-inviter-role"},
{"command": "chat +chat-create", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +chat-mute-member", "emitted": "user-ids", "expect": "users", "via": "override:scoped-mixed-id-list"},
{"command": "chat +chat-mute-member", "emitted": "user-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +chat-remove-bot", "emitted": "open-bot-id", "expect": "bot-id", "via": "concept:open_bot_id"},
{"command": "chat +chat-role-remove", "emitted": "role-ids", "expect": "did-you-mean:blocked", "via": "guard:list-vs-single"},
{"command": "chat +chat-role-remove-user", "emitted": "open-dingtalk-id", "expect": "user", "via": "override:scoped-mixed-id"},
{"command": "chat +chat-transfer-owner", "emitted": "user-id", "expect": "new-owner", "via": "override:scoped-owner-role"},
{"command": "chat +feed-group-query-item", "emitted": "chat-ids", "expect": "conversation-ids", "via": "override:scoped-explicit-cid-list"},
{"command": "chat +feed-group-query-item", "emitted": "conversation-id", "expect": "did-you-mean:blocked", "via": "guard:single-vs-list"},
{"command": "chat +messages-batch-recall-by-bot", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:process-query-key-vs-open-message-id"},
{"command": "chat +messages-combine-forward", "emitted": "src-open-cid", "expect": "src-conversation-id", "via": "override:scoped-source-role"},
{"command": "chat +messages-combine-forward", "emitted": "conversation-id", "expect": "did-you-mean:ambiguous", "via": "guard:source-vs-destination-role"},
{"command": "chat +messages-forward", "emitted": "source-message-id", "expect": "msg-id", "via": "override:scoped-source-role"},
{"command": "chat +messages-forward", "emitted": "conversation-id", "expect": "did-you-mean:ambiguous", "via": "guard:source-vs-destination-role"},
{"command": "chat +messages-forward-topic", "emitted": "src-open-message-id", "expect": "src-msg-id", "via": "override:scoped-source-role"},
{"command": "chat +messages-forward-topic", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:message-source-role"},
{"command": "chat +messages-recall-by-bot", "emitted": "message-id", "expect": "did-you-mean:blocked", "via": "guard:process-query-key-vs-open-message-id"},
{"command": "chat +messages-resource-download", "emitted": "conversation-id", "expect": "open-conversation-id", "via": "concept:open_conversation_id"},
{"command": "chat +messages-resource-download", "emitted": "download-dir", "expect": "did-you-mean:blocked", "via": "guard:output-file-or-directory-contract"},
{"command": "chat +messages-set-pin", "emitted": "conversation-id", "expect": "open-conversation-id", "via": "concept:open_conversation_id"}
]
}
}
@@ -0,0 +1,247 @@
{
"$schema": "./command_path_fallbacks.schema.json",
"version": 1,
"entries": [
{
"from": "chat +group-search",
"mode": "rewrite",
"to": "chat +chat-search",
"reviewed": true,
"review_reason": "0803 evaluation badcase: the model emitted +group-search with --query; +chat-search provides the same group-name search operation."
},
{
"from": "chat +members",
"mode": "rewrite",
"to": "chat +group-members",
"reviewed": true,
"review_reason": "20260728 evaluation badcases emitted +members three times for listing members of a group selected by name; +group-members is the unique reviewed read-only shortcut for that intent."
},
{
"from": "chat +group-member-list",
"mode": "rewrite",
"to": "chat +group-members",
"reviewed": true,
"review_reason": "20260728 evaluation emitted +group-member-list for a group-name member lookup; +group-members is the unique reviewed read-only shortcut and canonical parameter validation remains authoritative."
},
{
"from": "chat +list-group-bots",
"mode": "rewrite",
"to": "chat +chat-bots",
"reviewed": true,
"review_reason": "20260728 evaluation emitted +list-group-bots for listing robots in one group; +chat-bots is the unique reviewed read-only shortcut. The fallback preserves flags and does not reinterpret generic IDs."
},
{
"from": "chat +list-robot",
"mode": "rewrite",
"to": "chat +chat-bots",
"reviewed": true,
"review_reason": "20260728 evaluation emitted singular +list-robot for listing robots in one group; +chat-bots is the unique reviewed read-only shortcut. The fallback preserves flags and does not reinterpret generic IDs."
},
{
"from": "chat +list-robots",
"mode": "rewrite",
"to": "chat +chat-bots",
"reviewed": true,
"review_reason": "20260728 evaluation emitted +list-robots for listing robots in one group; +chat-bots is the unique reviewed read-only shortcut. The fallback preserves flags and does not reinterpret generic IDs."
},
{
"from": "chat +message-list",
"mode": "ambiguous",
"candidates": [
"chat +chat-messages",
"chat +messages-list-direct",
"chat +search-msg",
"chat +unread-chats"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +message-list without identifying group versus direct history, conversation history versus cross-chat search, or ordinary versus unread conversations; no candidate may be selected automatically."
},
{
"from": "chat +read-single",
"mode": "ambiguous",
"candidates": [
"chat +messages-list-direct",
"chat +chat-messages"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +read-single six times for direct-message history, but the invented name does not choose between the focused direct-history shortcut and the broader group/direct history workflow; command recovery must stop before parameter validation or dispatch."
},
{
"from": "chat +rename-group",
"mode": "rewrite",
"to": "chat +chat-update",
"reviewed": true,
"review_reason": "20260728 evaluation emitted +rename-group for the unique group-name update intent; +chat-update is the reviewed shortcut with that exact command-level operation. Parameter compatibility remains the canonical target's responsibility."
},
{
"from": "chat +send",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat +dm",
"chat +send-to-group"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send without a stable recipient type or sending identity; the write operation must stop until the caller chooses the unified, resolved-direct, or resolved-group workflow."
},
{
"from": "chat +send-message",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat +dm",
"chat +send-to-group"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-message without a stable recipient type or sending identity; the write operation must stop until the caller chooses the unified, resolved-direct, or resolved-group workflow."
},
{
"from": "chat +send-text",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat +dm",
"chat +send-to-group"
],
"reviewed": true,
"review_reason": "20260728 evaluation identified text content but not a stable recipient type or sending identity; the write operation must stop until the caller chooses the unified, resolved-direct, or resolved-group workflow."
},
{
"from": "chat +send-to",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat +dm",
"chat +send-to-group"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-to without proving whether the recipient denotes a user, group, or low-level identifier; the write operation must not choose a target workflow automatically."
},
{
"from": "chat +send-dm",
"mode": "ambiguous",
"candidates": [
"chat +dm",
"chat +messages-send"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-dm twice, but the invented name does not choose between the resolved-name text shortcut and the unified identifier-aware sending workflow; the write operation must not dispatch automatically."
},
{
"from": "chat +send-single",
"mode": "ambiguous",
"candidates": [
"chat +dm",
"chat +messages-send"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-single for a direct message, but the invented name does not choose between the resolved-name text shortcut and the unified identifier-aware sending workflow; the write operation must not dispatch automatically."
},
{
"from": "chat +send-by-bot",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat message send-by-bot"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-by-bot three times without a complete sending-identity contract; stop and present the unified identity-aware shortcut and the exact native bot sender instead of selecting a write path."
},
{
"from": "chat +group-send-text",
"mode": "ambiguous",
"candidates": [
"chat +send-to-group",
"chat +messages-send"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +group-send-text for a group text operation, but the invented name does not choose between name-resolved group text and the unified identifier-aware workflow; the write operation must not dispatch automatically."
},
{
"from": "chat +send-file",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat message send"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-file fifteen times with incompatible target and file parameter spellings; stop before dispatch and let the caller choose the unified shortcut or native current-user file workflow."
},
{
"from": "chat +send-image",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat message send"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-image without proving whether the input is an existing mediaId or a local file; stop before dispatch and let the caller choose the reviewed unified or native workflow."
},
{
"from": "chat +send-media",
"mode": "ambiguous",
"candidates": [
"chat +messages-send",
"chat message send"
],
"reviewed": true,
"review_reason": "20260728 evaluation emitted +send-media without a concrete media type, sending identity, or compatible parameter contract; stop before dispatch and let the caller choose the reviewed unified or native workflow."
},
{
"from": "oa +list-processes",
"mode": "ambiguous",
"candidates": [
"oa +list-forms",
"oa +my-initiated",
"oa approval list-initiated"
],
"reviewed": true,
"review_reason": "20260720 merged evaluation emitted +list-processes, but process can mean approval forms/templates or approval instances initiated by the current user; stop and present both shortcut workflows plus the exact native instance leaf."
},
{
"from": "chat +conversation-detail",
"mode": "rewrite",
"to": "chat +conversation-info",
"reviewed": true,
"review_reason": "20260804 multi-im badcase requested one conversation's details. +conversation-info is the unique current read-only shortcut for that operation. The rewrite changes only the command path and preserves every flag/value for target validation."
},
{
"from": "chat +bot-list",
"mode": "rewrite",
"to": "chat +chat-bots",
"reviewed": true,
"review_reason": "20260804 multi-im badcase requested the robot list for one group. +chat-bots is the unique current read-only shortcut. The fallback must not reinterpret the accompanying group flag."
},
{
"from": "chat +conversation-category-list",
"mode": "ambiguous",
"candidates": [
"chat +category-list",
"chat +category-list-conversations"
],
"reviewed": true,
"review_reason": "The invented name can mean listing the user's categories or listing conversations inside one category. No candidate may execute before the caller chooses the intended object level."
},
{
"from": "chat +conversation-group-list",
"mode": "ambiguous",
"candidates": [
"chat +category-list-conversations",
"chat +conversation-list"
],
"reviewed": true,
"review_reason": "The invented name can mean conversations in a custom category or the general conversation list. The command name alone does not identify the requested collection."
},
{
"from": "chat +list-my-groups",
"mode": "ambiguous",
"candidates": [
"chat +my-groups",
"chat +chat-list-mine",
"chat +chat-list"
],
"reviewed": true,
"review_reason": "The invented name does not choose between the established resolver shortcut, the legacy personal-group list and the current Schema-complete chat list. Recovery must stop instead of silently changing pagination or output semantics."
}
]
}

Some files were not shown because too many files have changed in this diff Show More