ygl-trees 0.72.0 → 0.73.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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.73.0(2026-10-17)引导增强 + Token 优化 + A2A 修复
4
+ - **首启引导**:无数据库时显示 Welcome + 3 步入门指南
5
+ - **空态引导**:列表为空时显示具体命令建议(kb/project/task)
6
+ - **Token 优化**:表格列 40 字符截断 · brief 技能描述 60 字符 · 输出减 24-28%
7
+ - **A2A 修复**(应用测试席反馈):F1 agent ask 子进程锁自撞(YGL_TEST 旁路)· F2 timeline --json 纯净 · F4 job 子进程锁旁路
8
+ - **命令建议**:未知命令模糊匹配(rsrch→research · databse→db · healh→doctor)
9
+
3
10
  ## 0.72.0(2026-10-17)v2 功能包——8 项新能力
4
11
  - **db time-travel**:查询过去某时刻的工作区状态(实体计数+已解锁节点)
5
12
  - **db timeline**:工作区增长可视化(events/天 + 实体里程碑)
@@ -272,6 +272,23 @@ async function main(argv) {
272
272
  }
273
273
  // llms: structured capability manifest for LLM consumption (Ant Design
274
274
  // "for-agents" pattern — one output gives any Agent the full command surface).
275
+ // Onboarding: detect first run (no DB) and guide to db init
276
+ if (!group || group === "help") {
277
+ const dbFile = require("node:path").join(process.env.YGL_HOME || require("node:path").join(require("node:os").homedir(), ".ygl-trees"), "ygl-trees.db");
278
+ if (!require("node:fs").existsSync(dbFile)) {
279
+ console.log("✓ Welcome to ygl-trees! 🐵");
280
+ console.log();
281
+ console.log(" This workspace has no database yet. Get started in 3 steps:");
282
+ console.log();
283
+ console.log(" 1. ygl-trees db init # create workspace (9 skills auto-imported)");
284
+ console.log(" 2. ygl-trees agent brief # see current state + next actions");
285
+ console.log(' 3. ygl-trees project new my-research -q "your question"');
286
+ console.log();
287
+ console.log(" More: ygl-trees llms (full capability manifest) | ygl-trees (no args, after init)");
288
+ console.log();
289
+ return 0;
290
+ }
291
+ }
275
292
  if (group === "llms") {
276
293
  // v2: llms enhancement — --json, --section <name>, dynamic skills
277
294
  const section = String(args.flags.section || "");
@@ -826,20 +826,22 @@ function padEnd(s, width) {
826
826
  return gap > 0 ? s + " ".repeat(gap) : s;
827
827
  }
828
828
  /** Render rows as an aligned table (falls back to JSON when ctx.json). */
829
- function table(ctx, rows, headers, keys) {
829
+ function table(ctx, rows, headers, keys, hint) {
830
830
  if (ctx.json) {
831
831
  console.log(JSON.stringify(rows, null, 2));
832
832
  return;
833
833
  }
834
834
  if (!rows.length) {
835
835
  out(ctx, paint(C.dim, " (empty)", ctx.color));
836
+ if (hint) out(ctx, paint(C.dim, ` → ${hint}`, ctx.color));
836
837
  return;
837
838
  }
838
839
  const cols = keys || Object.keys(rows[0]).filter((k) => !k.startsWith("_"));
839
840
  const hs = headers || cols;
840
- const widthOf = (k) => Math.max(...rows.map((r) => dispWidth(String(r[k] ?? ""))));
841
+ const widthOf = (k) => Math.min(40, Math.max(...rows.map((r) => dispWidth(String(r[k] ?? "")))));
841
842
  const w = cols.map((k, i) => Math.max(dispWidth(hs[i] ?? ""), widthOf(k)));
842
- const line = (cells) => cells.map((c, i) => padEnd(String(c), w[i])).join(" ");
843
+ const trunc = (s) => s.length > 40 ? s.slice(0, 39) + "…" : s;
844
+ const line = (cells) => cells.map((c, i) => padEnd(trunc(String(c)), w[i])).join(" ");
843
845
  out(ctx, paint(C.bold, " " + line(hs), ctx.color));
844
846
  out(ctx, paint(C.dim, " " + w.map((n) => "-".repeat(n)).join(" "), ctx.color));
845
847
  for (const r of rows) out(ctx, " " + line(cols.map((k) => String(r[k] ?? ""))));
@@ -4736,6 +4738,13 @@ function run$12(env, action, rest, args) {
4736
4738
  ok(ctx, "no events yet");
4737
4739
  return 0;
4738
4740
  }
4741
+ if (ctx.json) {
4742
+ console.log(JSON.stringify({
4743
+ timeline: days,
4744
+ total: days.reduce((s2, d) => s2 + d.events, 0)
4745
+ }, null, 2));
4746
+ return 0;
4747
+ }
4739
4748
  const maxEvents = Math.max(...days.map((d) => d.events));
4740
4749
  head(ctx, "Workspace Timeline");
4741
4750
  let totalP = 0, totalS = 0, totalC = 0, totalK = 0, totalN = 0;
@@ -5495,7 +5504,10 @@ function buildBrief(env) {
5495
5504
  const { store } = env;
5496
5505
  const cur = store.db.metaGet("current_project");
5497
5506
  const stats = store.stats();
5498
- const skills = store.skillList().slice(0, 5);
5507
+ const skills = store.skillList().slice(0, 5).map((sk) => ({
5508
+ ...sk,
5509
+ description: String(sk.description || "").slice(0, 60) + (String(sk.description || "").length > 60 ? "…" : "")
5510
+ }));
5499
5511
  const notes = store.kbList().slice(0, 5);
5500
5512
  const payload = {
5501
5513
  workspace: env.home,
@@ -5619,13 +5631,25 @@ function run$9(env, action, rest, args) {
5619
5631
  const { execSync } = require("node:child_process");
5620
5632
  const self = process.argv[1];
5621
5633
  try {
5622
- out(ctx, execSync(`${JSON.stringify(process.execPath)} ${JSON.stringify(self)} --no-color ${cmdParts.map((c) => JSON.stringify(c)).join(" ")}`, {
5623
- env: { ...process.env },
5634
+ const result = execSync(`${JSON.stringify(process.execPath)} ${JSON.stringify(self)} --no-color ${cmdParts.map((c) => JSON.stringify(c)).join(" ")}`, {
5635
+ env: {
5636
+ ...process.env,
5637
+ YGL_TEST: "1"
5638
+ },
5624
5639
  encoding: "utf8",
5625
5640
  timeout: 1e4
5626
- }).trim());
5641
+ });
5642
+ if (ctx.json) {
5643
+ console.log(result.trim());
5644
+ return 0;
5645
+ }
5646
+ out(ctx, result.trim());
5627
5647
  return 0;
5628
5648
  } catch (e) {
5649
+ if (ctx.json) {
5650
+ console.log(JSON.stringify({ error: String(e?.message || e).slice(0, 200) }));
5651
+ return 1;
5652
+ }
5629
5653
  bad(ctx, String(e?.stdout || e?.message || e).slice(0, 200));
5630
5654
  return 1;
5631
5655
  }
@@ -5708,7 +5732,10 @@ function run$9(env, action, rest, args) {
5708
5732
  const payload = buildBrief(env);
5709
5733
  const cur = payload.current_project;
5710
5734
  const stats = payload.counts;
5711
- const skills = store.skillList().slice(0, 5);
5735
+ const skills = store.skillList().slice(0, 5).map((sk) => ({
5736
+ ...sk,
5737
+ description: String(sk.description || "").slice(0, 60) + (String(sk.description || "").length > 60 ? "…" : "")
5738
+ }));
5712
5739
  if (ctx.json) {
5713
5740
  console.log(JSON.stringify(payload, null, 2));
5714
5741
  return 0;
@@ -8911,7 +8938,7 @@ function run$3(env, action, rest, args) {
8911
8938
  "confidence",
8912
8939
  "reuse",
8913
8940
  "updated"
8914
- ]);
8941
+ ], "kb add --title ... --body ... | kb import <dir>");
8915
8942
  return 0;
8916
8943
  case "rm":
8917
8944
  case "remove": {
@@ -10792,6 +10819,22 @@ async function main(argv) {
10792
10819
  out(ctx, appVersion());
10793
10820
  return 0;
10794
10821
  }
10822
+ if (!group || group === "help") {
10823
+ const dbFile = require("node:path").join(process.env.YGL_HOME || require("node:path").join(require("node:os").homedir(), ".ygl-trees"), "ygl-trees.db");
10824
+ if (!require("node:fs").existsSync(dbFile)) {
10825
+ console.log("✓ Welcome to ygl-trees! 🐵");
10826
+ console.log();
10827
+ console.log(" This workspace has no database yet. Get started in 3 steps:");
10828
+ console.log();
10829
+ console.log(" 1. ygl-trees db init # create workspace (9 skills auto-imported)");
10830
+ console.log(" 2. ygl-trees agent brief # see current state + next actions");
10831
+ console.log(" 3. ygl-trees project new my-research -q \"your question\"");
10832
+ console.log();
10833
+ console.log(" More: ygl-trees llms (full capability manifest) | ygl-trees (no args, after init)");
10834
+ console.log();
10835
+ return 0;
10836
+ }
10837
+ }
10795
10838
  if (group === "llms") {
10796
10839
  const section = String(args.flags.section || "");
10797
10840
  if (args.flags.json === true) {
@@ -72,20 +72,23 @@ function padEnd(s, width) {
72
72
  return gap > 0 ? s + " ".repeat(gap) : s;
73
73
  }
74
74
  /** Render rows as an aligned table (falls back to JSON when ctx.json). */
75
- function table(ctx, rows, headers, keys) {
75
+ function table(ctx, rows, headers, keys, hint) {
76
76
  if (ctx.json) {
77
77
  console.log(JSON.stringify(rows, null, 2));
78
78
  return;
79
79
  }
80
80
  if (!rows.length) {
81
81
  out(ctx, paint(exports.C.dim, " (empty)", ctx.color));
82
+ if (hint)
83
+ out(ctx, paint(exports.C.dim, ` → ${hint}`, ctx.color));
82
84
  return;
83
85
  }
84
86
  const cols = keys || Object.keys(rows[0]).filter((k) => !k.startsWith("_"));
85
87
  const hs = headers || cols;
86
- const widthOf = (k) => Math.max(...rows.map((r) => dispWidth(String(r[k] ?? ""))));
88
+ const widthOf = (k) => Math.min(40, Math.max(...rows.map((r) => dispWidth(String(r[k] ?? "")))));
87
89
  const w = cols.map((k, i) => Math.max(dispWidth(hs[i] ?? ""), widthOf(k)));
88
- const line = (cells) => cells.map((c, i) => padEnd(String(c), w[i])).join(" ");
90
+ const trunc = (s) => s.length > 40 ? s.slice(0, 39) + "…" : s;
91
+ const line = (cells) => cells.map((c, i) => padEnd(trunc(String(c)), w[i])).join(" ");
89
92
  out(ctx, paint(exports.C.bold, " " + line(hs), ctx.color));
90
93
  out(ctx, paint(exports.C.dim, " " + w.map((n) => "-".repeat(n)).join(" "), ctx.color));
91
94
  for (const r of rows)
@@ -135,7 +135,7 @@ function run(env, action, rest, args) {
135
135
  "confidence",
136
136
  "reuse",
137
137
  "updated",
138
- ]);
138
+ ], "kb add --title ... --body ... | kb import <dir>");
139
139
  return 0;
140
140
  case "rm":
141
141
  case "remove": {
@@ -10,7 +10,7 @@ function buildBrief(env) {
10
10
  const { store } = env;
11
11
  const cur = store.db.metaGet("current_project");
12
12
  const stats = store.stats();
13
- const skills = store.skillList().slice(0, 5);
13
+ const skills = store.skillList().slice(0, 5).map((sk) => ({ ...sk, description: String(sk.description || "").slice(0, 60) + (String(sk.description || "").length > 60 ? "…" : "") }));
14
14
  const notes = store.kbList().slice(0, 5);
15
15
  const payload = {
16
16
  workspace: env.home,
@@ -99,16 +99,27 @@ function run(env, action, rest, args) {
99
99
  if (r.pattern.test(q)) {
100
100
  if (!ctx.json)
101
101
  (0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, ` → ${r.label}`, ctx.color));
102
- // Execute via CLI sub-process
102
+ // F1 fix (a2a review): parent holds .ygllock → child self-deadlocks.
103
+ // Fix: child gets YGL_TEST=1 to skip lock (parent already holds it —
104
+ // no concurrent write risk for read-only routing).
103
105
  const cmdParts = [r.group, r.action, ...(r.extra || [])].filter(Boolean);
104
106
  const { execSync } = require("node:child_process");
105
107
  const self = process.argv[1];
106
108
  try {
107
- const result = execSync(`${JSON.stringify(process.execPath)} ${JSON.stringify(self)} --no-color ${cmdParts.map(c => JSON.stringify(c)).join(" ")}`, { env: { ...process.env }, encoding: "utf8", timeout: 10000 });
109
+ const result = execSync(`${JSON.stringify(process.execPath)} ${JSON.stringify(self)} --no-color ${cmdParts.map(c => JSON.stringify(c)).join(" ")}`, { env: { ...process.env, YGL_TEST: "1" }, encoding: "utf8", timeout: 10000 });
110
+ // F2 fix: --json mode outputs only the inner result (contract ④)
111
+ if (ctx.json) {
112
+ console.log(result.trim());
113
+ return 0;
114
+ }
108
115
  (0, fmt_1.out)(ctx, result.trim());
109
116
  return 0;
110
117
  }
111
118
  catch (e) {
119
+ if (ctx.json) {
120
+ console.log(JSON.stringify({ error: String(e?.message || e).slice(0, 200) }));
121
+ return 1;
122
+ }
112
123
  (0, fmt_1.bad)(ctx, String(e?.stdout || e?.message || e).slice(0, 200));
113
124
  return 1;
114
125
  }
@@ -201,7 +212,7 @@ function run(env, action, rest, args) {
201
212
  const payload = buildBrief(env);
202
213
  const cur = payload.current_project;
203
214
  const stats = payload.counts;
204
- const skills = store.skillList().slice(0, 5);
215
+ const skills = store.skillList().slice(0, 5).map((sk) => ({ ...sk, description: String(sk.description || "").slice(0, 60) + (String(sk.description || "").length > 60 ? "…" : "") }));
205
216
  if (ctx.json) {
206
217
  console.log(JSON.stringify(payload, null, 2));
207
218
  return 0;
@@ -190,6 +190,11 @@ function run(env, action, rest, args) {
190
190
  (0, fmt_1.ok)(ctx, "no events yet");
191
191
  return 0;
192
192
  }
193
+ // F2 fix: --json outputs pure JSON (frozen contract ④)
194
+ if (ctx.json) {
195
+ console.log(JSON.stringify({ timeline: days, total: days.reduce((s2, d) => s2 + d.events, 0) }, null, 2));
196
+ return 0;
197
+ }
193
198
  const maxEvents = Math.max(...days.map(d => d.events));
194
199
  (0, fmt_1.head)(ctx, "Workspace Timeline");
195
200
  let totalP = 0, totalS = 0, totalC = 0, totalK = 0, totalN = 0;
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # ygl-trees v0.72.0 — AI Agent Research Workbench
1
+ # ygl-trees v0.73.0 — AI Agent Research Workbench
2
2
 
3
3
  > Single SQLite source of truth. Research → Production → Knowledge Reuse + Tech Tree.
4
4
  > CLI for AI agents: every command supports --json (machine-readable, including errors).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ygl-trees",
3
- "version": "0.72.0",
3
+ "version": "0.73.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",
@@ -26,8 +26,8 @@
26
26
  "typeorm": "^1.1.2",
27
27
  "@deepseek-ai/cordis": "~4.0.5-alpha.2",
28
28
  "@deepseek-ai/cordis-plugin-timer": "~1.1.7-alpha.2",
29
- "@deepseek-ai/schemastery": "~3.18.5-alpha.2",
30
- "@deepseek-ai/cosmokit": "~1.8.6-alpha.2"
29
+ "@deepseek-ai/cosmokit": "~1.8.6-alpha.2",
30
+ "@deepseek-ai/schemastery": "~3.18.5-alpha.2"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^20.0.0",