ygl-trees 0.69.0 → 0.70.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
@@ -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;
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ygl-trees",
3
- "version": "0.69.0",
3
+ "version": "0.70.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,9 +25,9 @@
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",
28
+ "@deepseek-ai/schemastery": "~3.18.5-alpha.2",
29
29
  "@deepseek-ai/cosmokit": "~1.8.6-alpha.2",
30
- "@deepseek-ai/schemastery": "~3.18.5-alpha.2"
30
+ "@deepseek-ai/cordis-plugin-timer": "~1.1.7-alpha.2"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^20.0.0",