feat(coverage): 资产测试覆盖度(task_scope 范围 + fact 派生已测)

按 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]>
This commit is contained in:
Autumn-27andClaude Opus 5 committed 2026-08-08 09:09:38 -04:00
1 parent 58e057a3f2
commit aeadd0d48b
8 files changed
+499 -4

No files matched your search

+22 -1
View File
@@ -7,9 +7,9 @@ import (
"strconv"
"strings"
"github.com/Autumn-27/artex/db"
acperm "github.com/Autumn-27/norma/permission"
actool "github.com/Autumn-27/norma/tool"
"github.com/Autumn-27/artex/db"
)
// compactIntents distills intents to {id, summary, state, asset_ids, parents,
@@ -324,6 +324,25 @@ func (t *ToolSet) graphOverviewData() map[string]any {
if description, goal, err := t.ts.Root(); err == nil {
out["task"] = map[string]any{"description": description, "goal": goal}
}
// coverage:粗略的资产测试覆盖度参考——范围(task_scope)内的资产里,被 fact 碰过的
// 占比;backlog_sample 是范围内还没被测的资产(供优先补测)。仅任务上下文有。
if t.as != nil && t.ts != nil && t.taskID > 0 {
if cov, err := t.as.TaskCoverage(t.taskID, t.ts.ID(), 20); err == nil {
m := map[string]any{
"denominator": cov.Denominator,
"tested": cov.Tested,
"backlog_sample": cov.Backlog,
"note": "粗略估计、仅供参考:容器型资产/大量枚举会让它偏低,勿据此认为已测完;优先补测 backlog。",
}
if cov.ScopeRows == 0 {
m["pct"] = nil
m["status"] = "范围未锚定"
} else {
m["pct"] = cov.Pct
}
out["coverage"] = m
}
}
return out
}
}
@@ -1073,5 +1092,7 @@ func (t *ToolSet) PlannerTools() []actool.CoreTool {
t.addFinding(),
// list_companies:查看企业列表 + scope + 资产数(拿 company_id / 理解归属范围)。
t.listCompanies(),
// add_task_scope:主动把整根域/整公司/某子域/IP 纳入本任务测试范围(覆盖度分母)。
t.addTaskScope(),
}
}