Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d3a2976cf |
@@ -70,6 +70,7 @@
|
||||
<tr><td>节点列表 Shortcut 调错 Wiki MCP 服务。</td><td>真实后端 <code>success=false</code>,Mock/静态检查看不出。</td><td><span class="tag fixed">跨域路由</span> 明确调用 doc/list_nodes,并纳入真实 E2E。</td></tr>
|
||||
<tr><td>成员帮助宣称最大 200。</td><td>真实接口超过 50 直接参数错误。</td><td><span class="tag fixed">真实上限</span> Shortcut 与原子 Help 均改为 50,并在本地提前拒绝。</td></tr>
|
||||
<tr><td>成员写操作从最多 50 条、不可分页的名单推断成员存在或缺失。</td><td>目标在截断部分时会误报写失败,或把未验证的移除报告为已读回。</td><td><span class="tag fixed">终态证据</span> 只接受写接口 <code>success=true</code>,并在结果中明确 <code>readbackAvailable=false</code>。</td></tr>
|
||||
<tr><td>空间搜索的稳定工作流属性名与实际请求属性名不同。</td><td>直接改写已发布的 <code>query/limit</code> 会造成无版本 Schema 破坏;继续隐式转换又会让审计者误以为请求同名透传。</td><td><span class="tag fixed">显式复合适配</span> 最终 Schema 保留兼容属性并明确声明转换为 <code>keyword/pageSize</code>;回归测试同时锁定最终交付和精确请求参数。</td></tr>
|
||||
<tr><td>知识库名称帮助宣称最大 100。</td><td>真实接口超过 32 失败。</td><td><span class="tag fixed">真实上限</span> Help 与 Shortcut 校验统一为 32。</td></tr>
|
||||
<tr><td>复制/移动/创建只把无异常视为成功。</td><td>空确认、未知远端效果或移动未到目标仍可能被接受。</td><td><span class="tag fixed">读回证明</span> 在后端具备精确查询能力时检查 success、业务 ID、workspace/folder 等最终状态。</td></tr>
|
||||
</tbody></table>
|
||||
|
||||
@@ -140,8 +140,17 @@ func TestDeliveryShortcutProgressiveQueriesReturnCompleteContracts(t *testing.T)
|
||||
assertChatCatalogCompleteLeafContracts(t)
|
||||
}
|
||||
|
||||
func TestDeliveryWikiSpaceSearchPreservesHistoricalParameterProperties(t *testing.T) {
|
||||
func TestDeliveryWikiSpaceSearchDeclaresCompatibilityAdapter(t *testing.T) {
|
||||
leaf := executeShortcutSchemaQuery(t, "--cli-path", "wiki +space-search")
|
||||
if got := schemaContractString(leaf["interface_mode"]); got != "composite" {
|
||||
t.Fatalf("wiki +space-search interface_mode = %q, want composite", got)
|
||||
}
|
||||
reason := schemaContractString(leaf["interface_reason"])
|
||||
for _, fragment := range []string{"query/limit", "search_wikiSpaces.keyword/pageSize", "versioned Schema migration"} {
|
||||
if !strings.Contains(reason, fragment) {
|
||||
t.Fatalf("wiki +space-search interface_reason = %q, want fragment %q", reason, fragment)
|
||||
}
|
||||
}
|
||||
parameters := schemaContractMap(leaf["parameters"])
|
||||
for name, want := range map[string]string{"query": "query", "limit": "limit"} {
|
||||
parameter := parameters[name]
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
|
||||
const wikiCompositeReason = "Reviewed Wiki Shortcut composite: the executable CLI owns strict response validation, pagination projection, optional multi-step orchestration, read-back verification, and confirmation; no single MCP interface represents the complete command contract."
|
||||
|
||||
const wikiSpaceSearchCompositeReason = "Reviewed Wiki space-search compatibility adapter: the published workflow properties query/limit remain stable while execution translates them to search_wikiSpaces.keyword/pageSize; redirecting an existing non-empty property requires a versioned Schema migration."
|
||||
|
||||
func wikiContract(command, description, useWhen string, avoidWhen, examples []string, result *contract.ResultSpec, pagination *contract.PaginationSpec, params ...contract.ParamDecl) corecmd.ContractDecl {
|
||||
name := "shortcut_" + strings.ReplaceAll(strings.TrimPrefix(command, "+"), "-", "_")
|
||||
cliPath := "wiki " + command
|
||||
@@ -28,6 +30,11 @@ func wikiContract(command, description, useWhen string, avoidWhen, examples []st
|
||||
}
|
||||
}
|
||||
|
||||
func wikiWithInterfaceReason(declared shortcut.Shortcut, reason string) shortcut.Shortcut {
|
||||
declared.Contract.Interface.Reason = reason
|
||||
return declared
|
||||
}
|
||||
|
||||
func wikiObjectResult(description string) *contract.ResultSpec {
|
||||
return &contract.ResultSpec{Outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess}, DataSchema: json.RawMessage(fmt.Sprintf(`{"type":"object","description":%q,"additionalProperties":true}`, description))}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ var SpaceList = readShortcut("+space-list", "严格分页列出知识库", "浏
|
||||
return rt.Output(out)
|
||||
})
|
||||
|
||||
var SpaceSearch = readShortcut("+space-search", "严格搜索知识库", "按名称关键词定位知识库;严格验证搜索数组,避免内部异常被误报为零命中。", "spaces", "dws wiki +space-search --query \"产品文档\" --format json", []shortcut.Flag{
|
||||
var SpaceSearch = wikiWithInterfaceReason(readShortcut("+space-search", "严格搜索知识库", "按名称关键词定位知识库;严格验证搜索数组,避免内部异常被误报为零命中。", "spaces", "dws wiki +space-search --query \"产品文档\" --format json", []shortcut.Flag{
|
||||
{Name: "query", Type: shortcut.FlagString, Required: true, Desc: "搜索关键词"}, {Name: "limit", Type: shortcut.FlagString, Desc: "返回数量 1-20(默认 10)"},
|
||||
}, []contract.ParamDecl{{Name: "query", Property: "query"}, {Name: "limit", Property: "limit"}}, func(rt *shortcut.RuntimeContext) error {
|
||||
pageSize, err := wikiStringInt(rt, "limit", 10, 1, 20)
|
||||
@@ -71,7 +71,7 @@ var SpaceSearch = readShortcut("+space-search", "严格搜索知识库", "按名
|
||||
out := map[string]any{"count": len(spaces), "spaces": spaces}
|
||||
addWikiPagination(out, page)
|
||||
return rt.Output(out)
|
||||
})
|
||||
}), wikiSpaceSearchCompositeReason)
|
||||
|
||||
var SpaceGet = readShortcut("+space-get", "获取知识库详情", "已知 workspace ID 或知识库 URL 时读取并验证空间详情。", "", "dws wiki +space-get --workspace <workspaceId> --format json", []shortcut.Flag{{Name: "workspace", Type: shortcut.FlagString, Required: true, Desc: "知识库 ID 或 URL"}}, []contract.ParamDecl{{Name: "workspace", Property: "workspaceId"}}, func(rt *shortcut.RuntimeContext) error {
|
||||
data, err := rt.CallMCPData("wiki", "get_wikiSpace", map[string]any{"workspaceId": rt.Str("workspace")})
|
||||
|
||||
@@ -131,9 +131,18 @@ func TestCrossPlatformCoverageWikiSpaceWorkflows(t *testing.T) {
|
||||
"wiki/search_wikiSpaces": {`{"success":true,"wikiSpaces":[{"spaceId":"w2","spaceName":"Plan"}]}`},
|
||||
}}
|
||||
out, err := runWikiCoverageCLI(t, caller, "+space-search", "--query", "Plan", "--limit", "12")
|
||||
if err != nil || out["count"] != float64(1) || caller.calls[0].args["pageSize"] != 12 {
|
||||
if err != nil || out["count"] != float64(1) || len(caller.calls) != 1 {
|
||||
t.Fatalf("search output=%#v err=%v calls=%#v", out, err, caller.calls)
|
||||
}
|
||||
if got := caller.calls[0]; got.product != "wiki" || got.tool != "search_wikiSpaces" || len(got.args) != 2 || got.args["keyword"] != "Plan" || got.args["pageSize"] != 12 {
|
||||
t.Fatalf("search call = %#v, want exact keyword/pageSize request", got)
|
||||
}
|
||||
if _, exists := caller.calls[0].args["query"]; exists {
|
||||
t.Fatalf("search request leaked compatibility property query: %#v", caller.calls[0].args)
|
||||
}
|
||||
if _, exists := caller.calls[0].args["limit"]; exists {
|
||||
t.Fatalf("search request leaked compatibility property limit: %#v", caller.calls[0].args)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get requires business id", func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user