mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-27 17:03:49 +08:00
按 docs/任务覆盖度设计-最终版.md 实现(轻量版,粗估供 agent 参考):
- 新表 task_scope(覆盖度分母 + per-task 授权边界):CREATE TABLE IF NOT EXISTS 进
schema.sql,随 db.go 每次启动 Exec,旧库自动生效。
- db/task_scope.go:AddAutoScope(insertAssets 按资产类型保守入范围)、AddAgentScope
(add_task_scope 工具:company/root_domain/subdomain/ip/cidr)、ListTaskScope、
TaskCoverage(范围内资产中被 fact 节点锚定过的占比 + backlog 取样)。
- insertAssets 顶层循环按 worker 显式插入的资产类型自动入范围(source=auto);
side-effect 派生的资产不触发(钩子在 handler 顶层、派生在 db 层内部),范围不盲目扩大。
- add_task_scope 工具绑 planner,随 BuiltinToolSeeds 播种(新库/旧库皆 seed)。
- graph_overview 注入 coverage(denominator/tested/backlog_sample + 粗估提示)供 planner 参考。
- HTTP:GET /api/tasks/{id}/coverage、/scope;任务总览页覆盖度卡(含 backlog)。
- 修 toolcatalog_test(record_fact 绑定断言,早前 mainagent 加 recordFact 后未同步)。
验证:go build / vet / agent test / 前端 tsc 均通过。注:DB 层 SQL 尚未经 live Postgres 运行验证。
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
85 lines
2.8 KiB
Go
85 lines
2.8 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
actool "github.com/Autumn-27/norma/tool"
|
|
)
|
|
|
|
// TestBuiltinToolSeeds ensures the catalog builds from a nil-store ToolSet without
|
|
// panicking, has stable keys, unions agent bindings, and carries real descriptions.
|
|
func TestBuiltinToolSeeds(t *testing.T) {
|
|
seeds := BuiltinToolSeeds()
|
|
if len(seeds) == 0 {
|
|
t.Fatal("no seeds")
|
|
}
|
|
byKey := map[string]ToolSeed{}
|
|
for _, s := range seeds {
|
|
if s.Key == "" || s.Desc == "" {
|
|
t.Errorf("seed %q missing key/desc", s.Key)
|
|
}
|
|
byKey[s.Key] = s
|
|
}
|
|
// record_fact is bound to worker (also mainagent, which can log confirmed facts).
|
|
rf, ok := byKey["record_fact"]
|
|
hasWorker := false
|
|
for _, a := range rf.Agents {
|
|
if a == "worker" {
|
|
hasWorker = true
|
|
}
|
|
}
|
|
if !ok || !hasWorker {
|
|
t.Errorf("record_fact agents = %v, want to include worker", rf.Agents)
|
|
}
|
|
// add_task_scope is bound to the planner (deliberate scope widening).
|
|
if ts, ok := byKey["add_task_scope"]; !ok || len(ts.Agents) == 0 {
|
|
t.Errorf("add_task_scope not seeded / has no agent binding: %v", ts.Agents)
|
|
}
|
|
// SDK generic tools (incl. sleep, now part of DefaultTools) are deliberately NOT
|
|
// seeded — every agent owns them; they flow through ToolResolve untouched.
|
|
for _, k := range []string{"Bash", "Read", "Write", "Edit", "Grep", "sleep"} {
|
|
if _, ok := byKey[k]; ok {
|
|
t.Errorf("SDK tool %q should not be seeded", k)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDecorateToolInjectsDefaults verifies a schema "default" fills a missing param
|
|
// before the underlying handler runs, and an explicitly-provided value is kept.
|
|
func TestDecorateToolInjectsDefaults(t *testing.T) {
|
|
var seen map[string]any
|
|
base := actool.Build(actool.Spec{
|
|
Name: "probe", Description: "orig",
|
|
Run: func(_ context.Context, in json.RawMessage, _ *actool.ToolContext) (actool.Result, error) {
|
|
_ = json.Unmarshal(in, &seen)
|
|
return actool.Text("ok"), nil
|
|
},
|
|
})
|
|
schema := map[string]any{"type": "object", "properties": map[string]any{
|
|
"limit": map[string]any{"type": "integer", "description": "n", "default": float64(3)},
|
|
"q": map[string]any{"type": "string", "description": "query"},
|
|
}}
|
|
dec := DecorateTool(base, "new desc", schema)
|
|
if dec.Description() != "new desc" {
|
|
t.Errorf("description = %q", dec.Description())
|
|
}
|
|
|
|
// limit omitted → default 3 injected; q kept.
|
|
if _, err := dec.Call(context.Background(), json.RawMessage(`{"q":"x"}`), nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if seen["limit"] != float64(3) || seen["q"] != "x" {
|
|
t.Errorf("injected = %v, want limit=3 q=x", seen)
|
|
}
|
|
|
|
// limit provided → default does NOT override.
|
|
if _, err := dec.Call(context.Background(), json.RawMessage(`{"limit":9}`), nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if seen["limit"] != float64(9) {
|
|
t.Errorf("limit = %v, want 9 (no override)", seen["limit"])
|
|
}
|
|
}
|