ygl-trees 0.69.0 → 0.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.70.0(2026-10-17)`agent metrics`——从 R11 手动测量到自动化指标
|
|
4
|
+
- **`ygl-trees agent metrics`**:一条命令输出工作台全量量化指标——冷启动(brief 耗时/next actions/可用技能)、知识(总量/7日增长/增长率)、研究(来源/证据/断言/验证率/证据密度)、技能(总数/命中/均命中)、科技树(棵数/节点/解锁率)、活动(7 日 events)
|
|
5
|
+
- **来源**:R11 冷启动量化研究的手动测量 → 自动化——从 `time agent brief` + 手动 grep 统计变为一键输出
|
|
6
|
+
- 135 单测全绿
|
|
7
|
+
|
|
3
8
|
## 0.69.0(2026-10-17)MCP Server——R8 研究设计的工程落地
|
|
4
9
|
- **`ygl-trees mcp`**:启动 stdio MCP 服务器(JSON-RPC 2.0),Claude Desktop / Cursor / VS Code 等 IDE Agent 可直接连接
|
|
5
10
|
- **8 工具面**(三支柱理论驱动):agent_brief · agent_match · kb_search · claim_evaluate · tree_state · produce_report · doctor · task_advance
|
package/dist/bundle/bin.cjs
CHANGED
|
@@ -5351,6 +5351,72 @@ function buildBrief(env) {
|
|
|
5351
5351
|
function run$9(env, action, rest, args) {
|
|
5352
5352
|
const { ctx, store } = env;
|
|
5353
5353
|
switch (action) {
|
|
5354
|
+
case "metrics": {
|
|
5355
|
+
const t0 = Date.now();
|
|
5356
|
+
const brief = buildBrief(env);
|
|
5357
|
+
const briefMs = Date.now() - t0;
|
|
5358
|
+
const kbTotal = env.db.get("SELECT COUNT(*) AS n FROM kb_notes")?.n || 0;
|
|
5359
|
+
const kbRecent = env.db.get("SELECT COUNT(*) AS n FROM kb_notes WHERE created > datetime('now', '-7 days')")?.n || 0;
|
|
5360
|
+
const claims = env.db.get("SELECT COUNT(*) AS n FROM claims")?.n || 0;
|
|
5361
|
+
const claimsVerified = env.db.get("SELECT COUNT(*) AS n FROM claims WHERE status='supported'")?.n || 0;
|
|
5362
|
+
const evidence = env.db.get("SELECT COUNT(*) AS n FROM evidence")?.n || 0;
|
|
5363
|
+
const sources = env.db.get("SELECT COUNT(*) AS n FROM sources")?.n || 0;
|
|
5364
|
+
const skills = store.skillList();
|
|
5365
|
+
const skillHits = skills.reduce((s, x) => s + (x.hits || 0), 0);
|
|
5366
|
+
const trees = env.db.all("SELECT id, name FROM trees");
|
|
5367
|
+
let nodesTotal = 0, nodesUnlocked = 0;
|
|
5368
|
+
for (const t of trees) {
|
|
5369
|
+
const r = env.db.get("SELECT COUNT(*) AS total, SUM(CASE WHEN ns.status='unlocked' THEN 1 ELSE 0 END) AS unlocked FROM nodes n LEFT JOIN node_state ns ON ns.node_id=n.id AND ns.context='default' WHERE n.tree_id=?", [t.id]);
|
|
5370
|
+
nodesTotal += r?.total || 0;
|
|
5371
|
+
nodesUnlocked += r?.unlocked || 0;
|
|
5372
|
+
}
|
|
5373
|
+
const events7d = env.db.get("SELECT COUNT(*) AS n FROM events WHERE ts > datetime('now', '-7 days')")?.n || 0;
|
|
5374
|
+
const metrics = {
|
|
5375
|
+
cold_start: {
|
|
5376
|
+
brief_ms: briefMs,
|
|
5377
|
+
next_actions: brief.next_actions?.length || 0,
|
|
5378
|
+
skills_available: skills.length
|
|
5379
|
+
},
|
|
5380
|
+
knowledge: {
|
|
5381
|
+
total_notes: kbTotal,
|
|
5382
|
+
recent_7d: kbRecent,
|
|
5383
|
+
growth_rate_7d: kbTotal > 0 ? Math.round(kbRecent / kbTotal * 100) : 0
|
|
5384
|
+
},
|
|
5385
|
+
research: {
|
|
5386
|
+
sources,
|
|
5387
|
+
evidence,
|
|
5388
|
+
claims,
|
|
5389
|
+
verified_claims: claimsVerified,
|
|
5390
|
+
verification_rate: claims > 0 ? Math.round(claimsVerified / claims * 100) : 0,
|
|
5391
|
+
evidence_per_claim: claims > 0 ? (evidence / claims).toFixed(1) : "0"
|
|
5392
|
+
},
|
|
5393
|
+
skills: {
|
|
5394
|
+
total: skills.length,
|
|
5395
|
+
total_hits: skillHits,
|
|
5396
|
+
avg_hits: skills.length > 0 ? (skillHits / skills.length).toFixed(1) : "0"
|
|
5397
|
+
},
|
|
5398
|
+
trees: {
|
|
5399
|
+
total_trees: trees.length,
|
|
5400
|
+
nodes: nodesTotal,
|
|
5401
|
+
unlocked: nodesUnlocked,
|
|
5402
|
+
unlock_rate: nodesTotal > 0 ? Math.round(nodesUnlocked / nodesTotal * 100) : 0
|
|
5403
|
+
},
|
|
5404
|
+
activity: { events_7d: events7d },
|
|
5405
|
+
doctor: "run `doctor` for health checks"
|
|
5406
|
+
};
|
|
5407
|
+
if (ctx.json) {
|
|
5408
|
+
console.log(JSON.stringify(metrics, null, 2));
|
|
5409
|
+
return 0;
|
|
5410
|
+
}
|
|
5411
|
+
head(ctx, "ygl-trees metrics");
|
|
5412
|
+
out(ctx, ` 冷启动 brief ${metrics.cold_start.brief_ms}ms · ${metrics.cold_start.next_actions} next actions · ${metrics.cold_start.skills_available} skills`);
|
|
5413
|
+
out(ctx, ` 知识 ${metrics.knowledge.total_notes} notes (${metrics.knowledge.recent_7d} in 7d, ${metrics.knowledge.growth_rate_7d}% growth)`);
|
|
5414
|
+
out(ctx, ` 研究 ${metrics.research.sources} sources · ${metrics.research.evidence} evidence · ${metrics.research.verified_claims}/${metrics.research.claims} verified (${metrics.research.verification_rate}%)`);
|
|
5415
|
+
out(ctx, ` 技能 ${metrics.skills.total} skills · ${metrics.skills.total_hits} hits (avg ${metrics.skills.avg_hits})`);
|
|
5416
|
+
out(ctx, ` 科技树 ${metrics.trees.total_trees} trees · ${metrics.trees.unlocked}/${metrics.trees.nodes} unlocked (${metrics.trees.unlock_rate}%)`);
|
|
5417
|
+
out(ctx, ` 活动 ${metrics.activity.events_7d} events in 7d`);
|
|
5418
|
+
return 0;
|
|
5419
|
+
}
|
|
5354
5420
|
case "brief": {
|
|
5355
5421
|
const payload = buildBrief(env);
|
|
5356
5422
|
const cur = payload.current_project;
|
|
@@ -9090,6 +9156,61 @@ function gatherData(env) {
|
|
|
9090
9156
|
}));
|
|
9091
9157
|
}
|
|
9092
9158
|
}
|
|
9159
|
+
const kgEdges = [];
|
|
9160
|
+
const kgMap = /* @__PURE__ */ new Map();
|
|
9161
|
+
try {
|
|
9162
|
+
const rels = db.all("SELECT src_type, src_id, dst_type, dst_id, rel FROM relations LIMIT 200");
|
|
9163
|
+
const colors = {
|
|
9164
|
+
node: "#7c9885",
|
|
9165
|
+
claim: "#6a994e",
|
|
9166
|
+
kb: "#8b7355",
|
|
9167
|
+
evidence: "#daa520",
|
|
9168
|
+
artifact: "#5b8db8",
|
|
9169
|
+
skill: "#9b7cb8"
|
|
9170
|
+
};
|
|
9171
|
+
for (const r of rels) {
|
|
9172
|
+
for (const side of ["src", "dst"]) {
|
|
9173
|
+
const type = r[`${side}_type`];
|
|
9174
|
+
const id = r[`${side}_id`];
|
|
9175
|
+
const key = `${type}:${id}`;
|
|
9176
|
+
if (kgMap.has(key)) continue;
|
|
9177
|
+
let label = id;
|
|
9178
|
+
try {
|
|
9179
|
+
if (type === "node") {
|
|
9180
|
+
const n = db.get("SELECT name FROM nodes WHERE id=?", [id]);
|
|
9181
|
+
if (n) label = n.name;
|
|
9182
|
+
} else if (type === "claim") {
|
|
9183
|
+
const c = db.get("SELECT substr(text,1,40) AS t FROM claims WHERE id=?", [id]);
|
|
9184
|
+
if (c) label = c.t;
|
|
9185
|
+
} else if (type === "kb") {
|
|
9186
|
+
const k = db.get("SELECT title FROM kb_notes WHERE id=?", [id]);
|
|
9187
|
+
if (k) label = k.title.slice(0, 40);
|
|
9188
|
+
} else if (type === "evidence") {
|
|
9189
|
+
const e = db.get("SELECT substr(quote,1,40) AS q FROM evidence WHERE id=?", [id]);
|
|
9190
|
+
if (e) label = e.q;
|
|
9191
|
+
} else if (type === "artifact") {
|
|
9192
|
+
const a = db.get("SELECT title FROM artifacts WHERE id=?", [id]);
|
|
9193
|
+
if (a) label = a.title;
|
|
9194
|
+
}
|
|
9195
|
+
} catch {}
|
|
9196
|
+
kgMap.set(key, {
|
|
9197
|
+
id: key,
|
|
9198
|
+
label,
|
|
9199
|
+
type,
|
|
9200
|
+
color: colors[type] || "#999"
|
|
9201
|
+
});
|
|
9202
|
+
}
|
|
9203
|
+
kgEdges.push({
|
|
9204
|
+
source: `${r.src_type}:${r.src_id}`,
|
|
9205
|
+
target: `${r.dst_type}:${r.dst_id}`,
|
|
9206
|
+
label: r.rel
|
|
9207
|
+
});
|
|
9208
|
+
}
|
|
9209
|
+
} catch {}
|
|
9210
|
+
data.knowledge_graph = {
|
|
9211
|
+
nodes: Array.from(kgMap.values()),
|
|
9212
|
+
edges: kgEdges
|
|
9213
|
+
};
|
|
9093
9214
|
return data;
|
|
9094
9215
|
}
|
|
9095
9216
|
/** Single-file dashboard HTML. `live` adds a fetch bootstrap + auto-refresh. */
|
|
@@ -74,6 +74,78 @@ function buildBrief(env) {
|
|
|
74
74
|
function run(env, action, rest, args) {
|
|
75
75
|
const { ctx, store } = env;
|
|
76
76
|
switch (action) {
|
|
77
|
+
case "metrics": {
|
|
78
|
+
// R11 cold-start research → automated measurement. Health/efficiency
|
|
79
|
+
// metrics for the workspace — one command answers "how good is this
|
|
80
|
+
// workbench?" with quantified data.
|
|
81
|
+
const t0 = Date.now();
|
|
82
|
+
const brief = buildBrief(env);
|
|
83
|
+
const briefMs = Date.now() - t0;
|
|
84
|
+
// Knowledge metrics
|
|
85
|
+
const kbTotal = env.db.get("SELECT COUNT(*) AS n FROM kb_notes")?.n || 0;
|
|
86
|
+
const kbRecent = env.db.get("SELECT COUNT(*) AS n FROM kb_notes WHERE created > datetime('now', '-7 days')")?.n || 0;
|
|
87
|
+
// Research metrics
|
|
88
|
+
const claims = env.db.get("SELECT COUNT(*) AS n FROM claims")?.n || 0;
|
|
89
|
+
const claimsVerified = env.db.get("SELECT COUNT(*) AS n FROM claims WHERE status='supported'")?.n || 0;
|
|
90
|
+
const evidence = env.db.get("SELECT COUNT(*) AS n FROM evidence")?.n || 0;
|
|
91
|
+
const sources = env.db.get("SELECT COUNT(*) AS n FROM sources")?.n || 0;
|
|
92
|
+
// Skill metrics
|
|
93
|
+
const skills = store.skillList();
|
|
94
|
+
const skillHits = skills.reduce((s, x) => s + (x.hits || 0), 0);
|
|
95
|
+
// Tree metrics
|
|
96
|
+
const trees = env.db.all("SELECT id, name FROM trees");
|
|
97
|
+
let nodesTotal = 0, nodesUnlocked = 0;
|
|
98
|
+
for (const t of trees) {
|
|
99
|
+
const r = env.db.get("SELECT COUNT(*) AS total, SUM(CASE WHEN ns.status='unlocked' THEN 1 ELSE 0 END) AS unlocked FROM nodes n LEFT JOIN node_state ns ON ns.node_id=n.id AND ns.context='default' WHERE n.tree_id=?", [t.id]);
|
|
100
|
+
nodesTotal += r?.total || 0;
|
|
101
|
+
nodesUnlocked += r?.unlocked || 0;
|
|
102
|
+
}
|
|
103
|
+
// Event metrics
|
|
104
|
+
const events7d = env.db.get("SELECT COUNT(*) AS n FROM events WHERE ts > datetime('now', '-7 days')")?.n || 0;
|
|
105
|
+
const metrics = {
|
|
106
|
+
cold_start: {
|
|
107
|
+
brief_ms: briefMs,
|
|
108
|
+
next_actions: brief.next_actions?.length || 0,
|
|
109
|
+
skills_available: skills.length,
|
|
110
|
+
},
|
|
111
|
+
knowledge: {
|
|
112
|
+
total_notes: kbTotal,
|
|
113
|
+
recent_7d: kbRecent,
|
|
114
|
+
growth_rate_7d: kbTotal > 0 ? Math.round(kbRecent / kbTotal * 100) : 0,
|
|
115
|
+
},
|
|
116
|
+
research: {
|
|
117
|
+
sources, evidence, claims,
|
|
118
|
+
verified_claims: claimsVerified,
|
|
119
|
+
verification_rate: claims > 0 ? Math.round(claimsVerified / claims * 100) : 0,
|
|
120
|
+
evidence_per_claim: claims > 0 ? (evidence / claims).toFixed(1) : "0",
|
|
121
|
+
},
|
|
122
|
+
skills: {
|
|
123
|
+
total: skills.length,
|
|
124
|
+
total_hits: skillHits,
|
|
125
|
+
avg_hits: skills.length > 0 ? (skillHits / skills.length).toFixed(1) : "0",
|
|
126
|
+
},
|
|
127
|
+
trees: {
|
|
128
|
+
total_trees: trees.length,
|
|
129
|
+
nodes: nodesTotal,
|
|
130
|
+
unlocked: nodesUnlocked,
|
|
131
|
+
unlock_rate: nodesTotal > 0 ? Math.round(nodesUnlocked / nodesTotal * 100) : 0,
|
|
132
|
+
},
|
|
133
|
+
activity: { events_7d: events7d },
|
|
134
|
+
doctor: "run `doctor` for health checks",
|
|
135
|
+
};
|
|
136
|
+
if (ctx.json) {
|
|
137
|
+
console.log(JSON.stringify(metrics, null, 2));
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
(0, fmt_1.head)(ctx, "ygl-trees metrics");
|
|
141
|
+
(0, fmt_1.out)(ctx, ` 冷启动 brief ${metrics.cold_start.brief_ms}ms · ${metrics.cold_start.next_actions} next actions · ${metrics.cold_start.skills_available} skills`);
|
|
142
|
+
(0, fmt_1.out)(ctx, ` 知识 ${metrics.knowledge.total_notes} notes (${metrics.knowledge.recent_7d} in 7d, ${metrics.knowledge.growth_rate_7d}% growth)`);
|
|
143
|
+
(0, fmt_1.out)(ctx, ` 研究 ${metrics.research.sources} sources · ${metrics.research.evidence} evidence · ${metrics.research.verified_claims}/${metrics.research.claims} verified (${metrics.research.verification_rate}%)`);
|
|
144
|
+
(0, fmt_1.out)(ctx, ` 技能 ${metrics.skills.total} skills · ${metrics.skills.total_hits} hits (avg ${metrics.skills.avg_hits})`);
|
|
145
|
+
(0, fmt_1.out)(ctx, ` 科技树 ${metrics.trees.total_trees} trees · ${metrics.trees.unlocked}/${metrics.trees.nodes} unlocked (${metrics.trees.unlock_rate}%)`);
|
|
146
|
+
(0, fmt_1.out)(ctx, ` 活动 ${metrics.activity.events_7d} events in 7d`);
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
77
149
|
case "brief": {
|
|
78
150
|
const payload = buildBrief(env);
|
|
79
151
|
const cur = payload.current_project;
|
|
@@ -145,6 +145,59 @@ function gatherData(env) {
|
|
|
145
145
|
.map((r) => ({ id: r.id, kind: r.kind, title: r.title, version: r.version, created: r.created }));
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
+
// R19: knowledge graph — entities and relations from the rel fabric
|
|
149
|
+
const kgNodes = [];
|
|
150
|
+
const kgEdges = [];
|
|
151
|
+
const kgMap = new Map();
|
|
152
|
+
try {
|
|
153
|
+
const rels = db.all("SELECT src_type, src_id, dst_type, dst_id, rel FROM relations LIMIT 200");
|
|
154
|
+
const colors = {
|
|
155
|
+
node: "#7c9885", claim: "#6a994e", kb: "#8b7355",
|
|
156
|
+
evidence: "#daa520", artifact: "#5b8db8", skill: "#9b7cb8",
|
|
157
|
+
};
|
|
158
|
+
for (const r of rels) {
|
|
159
|
+
for (const side of ["src", "dst"]) {
|
|
160
|
+
const type = r[`${side}_type`];
|
|
161
|
+
const id = r[`${side}_id`];
|
|
162
|
+
const key = `${type}:${id}`;
|
|
163
|
+
if (kgMap.has(key))
|
|
164
|
+
continue;
|
|
165
|
+
let label = id;
|
|
166
|
+
try {
|
|
167
|
+
if (type === "node") {
|
|
168
|
+
const n = db.get("SELECT name FROM nodes WHERE id=?", [id]);
|
|
169
|
+
if (n)
|
|
170
|
+
label = n.name;
|
|
171
|
+
}
|
|
172
|
+
else if (type === "claim") {
|
|
173
|
+
const c = db.get("SELECT substr(text,1,40) AS t FROM claims WHERE id=?", [id]);
|
|
174
|
+
if (c)
|
|
175
|
+
label = c.t;
|
|
176
|
+
}
|
|
177
|
+
else if (type === "kb") {
|
|
178
|
+
const k = db.get("SELECT title FROM kb_notes WHERE id=?", [id]);
|
|
179
|
+
if (k)
|
|
180
|
+
label = k.title.slice(0, 40);
|
|
181
|
+
}
|
|
182
|
+
else if (type === "evidence") {
|
|
183
|
+
const e = db.get("SELECT substr(quote,1,40) AS q FROM evidence WHERE id=?", [id]);
|
|
184
|
+
if (e)
|
|
185
|
+
label = e.q;
|
|
186
|
+
}
|
|
187
|
+
else if (type === "artifact") {
|
|
188
|
+
const a = db.get("SELECT title FROM artifacts WHERE id=?", [id]);
|
|
189
|
+
if (a)
|
|
190
|
+
label = a.title;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch { /* best-effort */ }
|
|
194
|
+
kgMap.set(key, { id: key, label, type, color: colors[type] || "#999" });
|
|
195
|
+
}
|
|
196
|
+
kgEdges.push({ source: `${r.src_type}:${r.src_id}`, target: `${r.dst_type}:${r.dst_id}`, label: r.rel });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch { /* graph data best-effort */ }
|
|
200
|
+
data.knowledge_graph = { nodes: Array.from(kgMap.values()), edges: kgEdges };
|
|
148
201
|
return data;
|
|
149
202
|
}
|
|
150
203
|
/** Single-file dashboard HTML. `live` adds a fetch bootstrap + auto-refresh. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ygl-trees",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.71.0",
|
|
4
4
|
"description": "猿光 · 科技树 — database-centric CLI for research → production → knowledge reuse, built for AI agents.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/apps/cli/index.js",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"reflect-metadata": "^0.2.2",
|
|
26
26
|
"typeorm": "^1.1.2",
|
|
27
27
|
"@deepseek-ai/cordis": "~4.0.5-alpha.2",
|
|
28
|
-
"@deepseek-ai/cordis-plugin-timer": "~1.1.7-alpha.2",
|
|
29
28
|
"@deepseek-ai/cosmokit": "~1.8.6-alpha.2",
|
|
29
|
+
"@deepseek-ai/cordis-plugin-timer": "~1.1.7-alpha.2",
|
|
30
30
|
"@deepseek-ai/schemastery": "~3.18.5-alpha.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|