ygl-trees 0.71.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 +20 -0
- package/README.md +7 -2
- package/dist/apps/cli/src/bin.js +106 -1
- package/dist/bundle/bin.cjs +492 -7
- package/dist/packages/core/text/src/fmt.js +6 -3
- package/dist/packages/knowledge/skills/src/cmds.js +1 -1
- package/dist/packages/knowledge/skills/src/skill-cmds.js +64 -0
- package/dist/packages/ops/tools/src/agent-cmds.js +64 -2
- package/dist/packages/ops/tools/src/db-cmds.js +210 -0
- package/llms.txt +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
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
|
+
|
|
10
|
+
## 0.72.0(2026-10-17)v2 功能包——8 项新能力
|
|
11
|
+
- **db time-travel**:查询过去某时刻的工作区状态(实体计数+已解锁节点)
|
|
12
|
+
- **db timeline**:工作区增长可视化(events/天 + 实体里程碑)
|
|
13
|
+
- **db workspace list/create**:多工作区管理
|
|
14
|
+
- **db obsidian**:导出为 Obsidian markdown vault(frontmatter 兼容)
|
|
15
|
+
- **agent ask**:自然语言接口(关键词路由到 CLI 命令)
|
|
16
|
+
- **skill init**:模板创建新技能(引导式)
|
|
17
|
+
- **skill refs**:展示技能关联引用(L3 渐进披露)
|
|
18
|
+
- **llms --section/--json**:节选输出 + 结构化 JSON
|
|
19
|
+
- **help 引导**:未知命令模糊匹配建议(前缀+子序列+别名)
|
|
20
|
+
|
|
3
21
|
## 0.70.0(2026-10-17)`agent metrics`——从 R11 手动测量到自动化指标
|
|
4
22
|
- **`ygl-trees agent metrics`**:一条命令输出工作台全量量化指标——冷启动(brief 耗时/next actions/可用技能)、知识(总量/7日增长/增长率)、研究(来源/证据/断言/验证率/证据密度)、技能(总数/命中/均命中)、科技树(棵数/节点/解锁率)、活动(7 日 events)
|
|
5
23
|
- **来源**:R11 冷启动量化研究的手动测量 → 自动化——从 `time agent brief` + 手动 grep 统计变为一键输出
|
|
@@ -19,6 +37,8 @@
|
|
|
19
37
|
- **F3:taskSetStatus 更新 artifacts.updated**——任务状态变更现在同步写 updated 时间戳(v9 纪律补漏)
|
|
20
38
|
- **exit 清理加固**:`beforeExit` + `exit` 双钩子 + 3 次重试(Windows EBUSY 容忍)
|
|
21
39
|
- **vendor/cordis bin 字段清理**:bin.js 已删但声明残留——移除
|
|
40
|
+
- **发布面补遗(0.68.0 后置修)**:Gitea 私仓补发 `@nelsonlongxiang/ygl-trees@0.68.0`——d950bdf 声称 "+ Gitea" 实未落上(私仓曾停留 0.66.0,npmjs 侧本就 latest);tarball 直验通过(`.ygllock` 标记物在产物内、无 .npmrc/token 泄露)
|
|
41
|
+
- **pack-check.sh 语法修复 + 名字无关化**(3542ab3):b62e866 将 `export YGL_TEST=1` 插入 `REPO=$(...)` 命令替换中部,注释吞掉 `&& pwd)` → 子壳永不闭合、脚本必死——0.68.0 的 npmjs 发布实际绕过了发布门禁;修复后 tarball glob/BIN 发现兼容 scoped(Gitea)与 unscoped(npmjs)双名
|
|
22
42
|
|
|
23
43
|
## 0.67.0(2026-10-17)WebUI antd 迭代——Ant Design 组件库升级 React 客户端
|
|
24
44
|
- **antd v6 + @ant-design/icons**:专业组件库驱动的富客户端
|
package/README.md
CHANGED
|
@@ -235,7 +235,11 @@ CJK 检索是个坑:FTS5 默认 `unicode61` 会把整句中文当成一个 tok
|
|
|
235
235
|
|
|
236
236
|
- **写入幂等**:`claim / evidence / query / kb / job` 的写命令按内容 hash 判重,重复执行返回已存在行(`! duplicate, kept existing`),Agent 重试不产生脏数据;`--force` 显式保留重复(job 用标题序号后缀)
|
|
237
237
|
- **完整备份**:`db export <file>` 覆盖全部数据表(含七张科技树表)并输出逐表行数摘要;dump 自带 `PRAGMA foreign_keys` 开关,可直接重放恢复。`--lean` 跳过闭包/拓扑等派生表数据
|
|
238
|
-
- **PostgreSQL 镜像/迁移**:`db
|
|
238
|
+
- **PostgreSQL 镜像/迁移**:`db time-travel "<ts>" workspace state at past moment
|
|
239
|
+
db timeline growth visualization (events/day)
|
|
240
|
+
db workspace list|create multi-workspace management
|
|
241
|
+
db obsidian [dir] export as Obsidian markdown vault
|
|
242
|
+
db sync pg <url>` 把整库单向全量镜像到 PG(可刷新,供 SQL 工具/团队分析);`db migrate pg <url> [--fresh]` 一次性完整保真迁移(FK/UNIQUE/索引约束齐全,目标库可独立持有)。两者都不做双后端同写,中文检索在 PG 侧用 tsvector+GIN 等价召回(详见 docs/pg-compat.md)。url 可走环境变量 `YGL_PG_URL`;`pg` 为可选依赖,不用 PG 则零影响
|
|
239
243
|
- **TypeORM 查询层**(0.8.0 起,研究域试点 `project show`):实体是 schema v8 的类型化快照(`synchronize` 永久 false,迁移权威仍在 user_version 轨道;FTS5/PRAGMA 路径保持裸 SQL)。依赖 typeorm + better-sqlite3 + reflect-metadata
|
|
240
244
|
- **重复可见**:`db dups` 列出同内容多行(含迁移前存量重复)
|
|
241
245
|
- **自动迁移**:v2 老库首次打开自动升级 schema v3(补 content_hash 列 + 回填 + 唯一索引),无人工介入
|
|
@@ -259,7 +263,8 @@ ygl-trees job logs
|
|
|
259
263
|
```bash
|
|
260
264
|
ygl-trees agent brief # 我在哪、有什么、下一步做什么
|
|
261
265
|
ygl-trees agent match "…" # 这个任务该用哪个 skill
|
|
262
|
-
ygl-trees agent
|
|
266
|
+
ygl-trees agent ask "<question>" natural language interface
|
|
267
|
+
agent log # 审计事件流
|
|
263
268
|
```
|
|
264
269
|
|
|
265
270
|
`agent brief --json` 直接给出结构化状态和建议动作序列,Agent 可以照着执行。
|
package/dist/apps/cli/src/bin.js
CHANGED
|
@@ -272,7 +272,53 @@ 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") {
|
|
293
|
+
// v2: llms enhancement — --json, --section <name>, dynamic skills
|
|
294
|
+
const section = String(args.flags.section || "");
|
|
295
|
+
if (args.flags.json === true) {
|
|
296
|
+
const manifest = generateLlmsManifest();
|
|
297
|
+
const sections = {};
|
|
298
|
+
let current = "header";
|
|
299
|
+
for (const line of manifest.split("\n")) {
|
|
300
|
+
if (line.startsWith("## ")) {
|
|
301
|
+
current = line.slice(3).toLowerCase().replace(/\s+/g, "_");
|
|
302
|
+
sections[current] = line + "\n";
|
|
303
|
+
}
|
|
304
|
+
else if (sections[current] !== undefined)
|
|
305
|
+
sections[current] += line + "\n";
|
|
306
|
+
}
|
|
307
|
+
console.log(JSON.stringify({ version: appVersion(), sections }, null, 2));
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
if (section) {
|
|
311
|
+
const manifest = generateLlmsManifest();
|
|
312
|
+
const all = manifest.split("\n## ");
|
|
313
|
+
const match = all.find(s2 => s2.toLowerCase().startsWith(section.toLowerCase()));
|
|
314
|
+
if (match) {
|
|
315
|
+
(0, fmt_1.out)(ctx, "## " + match);
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
(0, fmt_1.bad)(ctx, `section not found: ${section}`);
|
|
319
|
+
(0, fmt_1.out)(ctx, ` available: ${all.map(s3 => s3.split("\n")[0]).join(" | ")}`);
|
|
320
|
+
return 1;
|
|
321
|
+
}
|
|
276
322
|
(0, fmt_1.out)(ctx, generateLlmsManifest());
|
|
277
323
|
return 0;
|
|
278
324
|
}
|
|
@@ -413,7 +459,66 @@ async function main(argv) {
|
|
|
413
459
|
}
|
|
414
460
|
const mod = domain_barrel_1.DOMAIN_MODULES[group];
|
|
415
461
|
if (!mod) {
|
|
416
|
-
|
|
462
|
+
// Help guidance: suggest closest match for unknown commands
|
|
463
|
+
const known = ["agent", "research", "produce", "kb", "skill", "tree", "node", "task", "rel", "plugin", "webui", "db", "doctor", "config", "job", "project", "mcp", "llms", "version", "help"];
|
|
464
|
+
const aliases = {
|
|
465
|
+
database: "db", data: "db", sqlite: "db",
|
|
466
|
+
knowledge: "kb", notes: "kb", notebook: "kb",
|
|
467
|
+
helth: "doctor", check: "doctor", health: "doctor",
|
|
468
|
+
server: "webui", dashboard: "webui", ui: "webui",
|
|
469
|
+
timeline: "db", "time-travel": "db", workspace: "db", obsidian: "db",
|
|
470
|
+
};
|
|
471
|
+
const input = String(group).toLowerCase();
|
|
472
|
+
const suggestions = [];
|
|
473
|
+
// 0. alias check (database→db, knowledge→kb, etc.)
|
|
474
|
+
if (aliases[input])
|
|
475
|
+
suggestions.push(aliases[input]);
|
|
476
|
+
// 0b. alias prefix (databse→database→db, knolge→knowledge→kb)
|
|
477
|
+
for (const [alias, cmd] of Object.entries(aliases)) {
|
|
478
|
+
if (alias.startsWith(input.slice(0, 4)) && !suggestions.includes(cmd)) {
|
|
479
|
+
suggestions.push(cmd);
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// 1. exact prefix match (strongest)
|
|
484
|
+
for (const k of known)
|
|
485
|
+
if (k.startsWith(input.slice(0, 3)) && !suggestions.includes(k))
|
|
486
|
+
suggestions.push(k);
|
|
487
|
+
// 2. subsequence match (input chars appear in order)
|
|
488
|
+
if (suggestions.length < 2) {
|
|
489
|
+
for (const k of known) {
|
|
490
|
+
if (suggestions.includes(k))
|
|
491
|
+
continue;
|
|
492
|
+
let ki = 0;
|
|
493
|
+
for (const ch of input) {
|
|
494
|
+
ki = k.indexOf(ch, ki);
|
|
495
|
+
if (ki === -1)
|
|
496
|
+
break;
|
|
497
|
+
ki++;
|
|
498
|
+
}
|
|
499
|
+
if (ki !== -1)
|
|
500
|
+
suggestions.push(k);
|
|
501
|
+
if (suggestions.length >= 3)
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// 3. known command contains input
|
|
506
|
+
if (suggestions.length < 2) {
|
|
507
|
+
for (const k of known)
|
|
508
|
+
if (k.includes(input) && !suggestions.includes(k))
|
|
509
|
+
suggestions.push(k);
|
|
510
|
+
}
|
|
511
|
+
if (suggestions.length === 1) {
|
|
512
|
+
(0, fmt_1.bad)(ctx, `unknown group: ${group} — did you mean "${suggestions[0]}"?`);
|
|
513
|
+
}
|
|
514
|
+
else if (suggestions.length > 1) {
|
|
515
|
+
(0, fmt_1.bad)(ctx, `unknown group: ${group} — did you mean: ${suggestions.slice(0, 3).join(" | ")}?`);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
(0, fmt_1.bad)(ctx, `unknown group: ${group}`);
|
|
519
|
+
}
|
|
520
|
+
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, " try: ygl-trees (no args) for full usage", ctx.color));
|
|
521
|
+
;
|
|
417
522
|
if (!ctx.json)
|
|
418
523
|
(0, fmt_1.out)(ctx, usage()); // usage text belongs to the human channel
|
|
419
524
|
return 2;
|
package/dist/bundle/bin.cjs
CHANGED
|
@@ -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
|
|
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] ?? ""))));
|
|
@@ -4668,6 +4670,217 @@ function run$12(env, action, rest, args) {
|
|
|
4668
4670
|
});
|
|
4669
4671
|
return 0;
|
|
4670
4672
|
}
|
|
4673
|
+
case "time-travel": {
|
|
4674
|
+
const when = rest[0];
|
|
4675
|
+
if (!when) {
|
|
4676
|
+
bad(ctx, "usage: db time-travel \"YYYY-MM-DD HH:MM\" (or \"YYYY-MM-DD\")");
|
|
4677
|
+
return 2;
|
|
4678
|
+
}
|
|
4679
|
+
const ts = when.includes(" ") ? when : when + " 23:59:59";
|
|
4680
|
+
const tables = [
|
|
4681
|
+
"projects",
|
|
4682
|
+
"sources",
|
|
4683
|
+
"evidence",
|
|
4684
|
+
"claims",
|
|
4685
|
+
"kb_notes",
|
|
4686
|
+
"skills",
|
|
4687
|
+
"trees",
|
|
4688
|
+
"nodes",
|
|
4689
|
+
"artifacts",
|
|
4690
|
+
"events"
|
|
4691
|
+
];
|
|
4692
|
+
const counts = {};
|
|
4693
|
+
for (const t of tables) try {
|
|
4694
|
+
counts[t] = db.get(`SELECT COUNT(*) as n FROM ${t} WHERE created <= ?`, [ts])?.n || 0;
|
|
4695
|
+
} catch {
|
|
4696
|
+
counts[t] = 0;
|
|
4697
|
+
}
|
|
4698
|
+
let unlockedAt = 0;
|
|
4699
|
+
try {
|
|
4700
|
+
unlockedAt = db.get(`SELECT COUNT(DISTINCT entity_id) as n FROM events WHERE kind='node.unlock' AND ts <= ?`, [ts])?.n || 0;
|
|
4701
|
+
} catch {}
|
|
4702
|
+
if (ctx.json) {
|
|
4703
|
+
console.log(JSON.stringify({
|
|
4704
|
+
timestamp: ts,
|
|
4705
|
+
counts,
|
|
4706
|
+
unlocked_nodes: unlockedAt
|
|
4707
|
+
}, null, 2));
|
|
4708
|
+
return 0;
|
|
4709
|
+
}
|
|
4710
|
+
head(ctx, `Workspace at ${ts}`);
|
|
4711
|
+
const labels = {
|
|
4712
|
+
projects: "项目",
|
|
4713
|
+
sources: "来源",
|
|
4714
|
+
evidence: "证据",
|
|
4715
|
+
claims: "断言",
|
|
4716
|
+
kb_notes: "知识",
|
|
4717
|
+
skills: "技能",
|
|
4718
|
+
trees: "树",
|
|
4719
|
+
nodes: "节点",
|
|
4720
|
+
artifacts: "产出",
|
|
4721
|
+
events: "事件"
|
|
4722
|
+
};
|
|
4723
|
+
for (const [k, v] of Object.entries(counts)) out(ctx, ` ${String(labels[k] || k).padEnd(6)} ${String(v).padStart(5)}`);
|
|
4724
|
+
out(ctx, ` 已解锁 ${String(unlockedAt).padStart(5)}`);
|
|
4725
|
+
return 0;
|
|
4726
|
+
}
|
|
4727
|
+
case "timeline": {
|
|
4728
|
+
const days = db.all(`
|
|
4729
|
+
SELECT date(ts) as day, COUNT(*) as events,
|
|
4730
|
+
SUM(CASE WHEN kind LIKE 'project%' THEN 1 ELSE 0 END) as projects,
|
|
4731
|
+
SUM(CASE WHEN kind LIKE 'source%' THEN 1 ELSE 0 END) as sources,
|
|
4732
|
+
SUM(CASE WHEN kind LIKE 'claim%' THEN 1 ELSE 0 END) as claims,
|
|
4733
|
+
SUM(CASE WHEN kind LIKE 'kb%' THEN 1 ELSE 0 END) as kb,
|
|
4734
|
+
SUM(CASE WHEN kind LIKE 'node%' THEN 1 ELSE 0 END) as nodes
|
|
4735
|
+
FROM events GROUP BY date(ts) ORDER BY day
|
|
4736
|
+
`);
|
|
4737
|
+
if (!days.length) {
|
|
4738
|
+
ok(ctx, "no events yet");
|
|
4739
|
+
return 0;
|
|
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
|
+
}
|
|
4748
|
+
const maxEvents = Math.max(...days.map((d) => d.events));
|
|
4749
|
+
head(ctx, "Workspace Timeline");
|
|
4750
|
+
let totalP = 0, totalS = 0, totalC = 0, totalK = 0, totalN = 0;
|
|
4751
|
+
for (const d of days) {
|
|
4752
|
+
totalP += d.projects;
|
|
4753
|
+
totalS += d.sources;
|
|
4754
|
+
totalC += d.claims;
|
|
4755
|
+
totalK += d.kb;
|
|
4756
|
+
totalN += d.nodes;
|
|
4757
|
+
const barLen = Math.round(d.events / maxEvents * 30);
|
|
4758
|
+
const bar = "█".repeat(barLen);
|
|
4759
|
+
const milestones = [];
|
|
4760
|
+
if (d.projects > 0) milestones.push(`+${d.projects}项目`);
|
|
4761
|
+
if (d.sources > 0) milestones.push(`+${d.sources}来源`);
|
|
4762
|
+
if (d.claims > 0) milestones.push(`+${d.claims}断言`);
|
|
4763
|
+
if (d.kb > 0) milestones.push(`+${d.kb}知识`);
|
|
4764
|
+
if (d.nodes > 0) milestones.push(`+${d.nodes}节点`);
|
|
4765
|
+
out(ctx, ` ${d.day} ${bar.padEnd(32)} ${String(d.events).padStart(5)} events ${milestones.join(" ")}`);
|
|
4766
|
+
}
|
|
4767
|
+
out(ctx, "");
|
|
4768
|
+
out(ctx, paint(C.dim, ` 累计: ${totalP}项目 · ${totalS}来源 · ${totalC}断言 · ${totalK}知识 · ${totalN}节点变更`, ctx.color));
|
|
4769
|
+
return 0;
|
|
4770
|
+
}
|
|
4771
|
+
case "workspace": {
|
|
4772
|
+
const sub = rest[0] || "list";
|
|
4773
|
+
const wsRoot = node_path.join(env.home, "..");
|
|
4774
|
+
if (sub === "list") {
|
|
4775
|
+
const dirs = node_fs.existsSync(wsRoot) ? node_fs.readdirSync(wsRoot).filter((d) => node_fs.existsSync(node_path.join(wsRoot, d, "ygl-trees.db"))) : [];
|
|
4776
|
+
const cur = node_path.basename(env.home);
|
|
4777
|
+
head(ctx, "Workspaces (parent: " + wsRoot + ")");
|
|
4778
|
+
for (const d of dirs) out(ctx, (d === cur ? " * " : " ") + d);
|
|
4779
|
+
out(ctx, "");
|
|
4780
|
+
out(ctx, paint(C.dim, " * = current | switch: YGL_HOME=<path> ygl-trees <cmd>", ctx.color));
|
|
4781
|
+
return 0;
|
|
4782
|
+
}
|
|
4783
|
+
if (sub === "create") {
|
|
4784
|
+
const name = rest[1];
|
|
4785
|
+
if (!name) {
|
|
4786
|
+
bad(ctx, "usage: db workspace create <name>");
|
|
4787
|
+
return 2;
|
|
4788
|
+
}
|
|
4789
|
+
const dir = node_path.join(wsRoot, name);
|
|
4790
|
+
if (node_fs.existsSync(node_path.join(dir, "ygl-trees.db"))) {
|
|
4791
|
+
ok(ctx, `workspace already exists: ${name}`);
|
|
4792
|
+
return 0;
|
|
4793
|
+
}
|
|
4794
|
+
node_fs.mkdirSync(dir, { recursive: true });
|
|
4795
|
+
ok(ctx, `workspace created: ${dir}`);
|
|
4796
|
+
out(ctx, paint(C.dim, ` init: YGL_HOME=${dir} ygl-trees db init`, ctx.color));
|
|
4797
|
+
return 0;
|
|
4798
|
+
}
|
|
4799
|
+
bad(ctx, "usage: db workspace list | create <name>");
|
|
4800
|
+
return 2;
|
|
4801
|
+
}
|
|
4802
|
+
case "obsidian": {
|
|
4803
|
+
const outDir = rest[0] || node_path.join(env.home, "obsidian-export");
|
|
4804
|
+
node_fs.mkdirSync(node_path.join(outDir, "projects"), { recursive: true });
|
|
4805
|
+
node_fs.mkdirSync(node_path.join(outDir, "knowledge"), { recursive: true });
|
|
4806
|
+
node_fs.mkdirSync(node_path.join(outDir, "skills"), { recursive: true });
|
|
4807
|
+
node_fs.mkdirSync(node_path.join(outDir, "trees"), { recursive: true });
|
|
4808
|
+
node_fs.mkdirSync(node_path.join(outDir, "claims"), { recursive: true });
|
|
4809
|
+
let count = 0;
|
|
4810
|
+
const safe = (t) => String(t).replace(/[\/:*?"<>|]/g, "_").slice(0, 50);
|
|
4811
|
+
for (const p of db.all("SELECT * FROM projects")) {
|
|
4812
|
+
node_fs.writeFileSync(node_path.join(outDir, "projects", safe(p.name) + ".md"), `---
|
|
4813
|
+
type: project
|
|
4814
|
+
id: ${p.id}
|
|
4815
|
+
---
|
|
4816
|
+
|
|
4817
|
+
# ${p.name}
|
|
4818
|
+
|
|
4819
|
+
${p.question || ""}
|
|
4820
|
+
`);
|
|
4821
|
+
count++;
|
|
4822
|
+
}
|
|
4823
|
+
for (const n of db.all("SELECT * FROM kb_notes")) {
|
|
4824
|
+
node_fs.writeFileSync(node_path.join(outDir, "knowledge", safe(n.title) + ".md"), `---
|
|
4825
|
+
type: knowledge
|
|
4826
|
+
tags: ${n.tags || ""}
|
|
4827
|
+
confidence: ${n.confidence}
|
|
4828
|
+
---
|
|
4829
|
+
|
|
4830
|
+
# ${n.title}
|
|
4831
|
+
|
|
4832
|
+
${n.body}
|
|
4833
|
+
`);
|
|
4834
|
+
count++;
|
|
4835
|
+
}
|
|
4836
|
+
for (const sk of db.all("SELECT * FROM skills WHERE enabled=1")) {
|
|
4837
|
+
node_fs.writeFileSync(node_path.join(outDir, "skills", safe(sk.name) + ".md"), `---
|
|
4838
|
+
type: skill
|
|
4839
|
+
version: ${sk.version}
|
|
4840
|
+
---
|
|
4841
|
+
|
|
4842
|
+
# ${sk.name}
|
|
4843
|
+
|
|
4844
|
+
${String(sk.body).slice(0, 500)}
|
|
4845
|
+
`);
|
|
4846
|
+
count++;
|
|
4847
|
+
}
|
|
4848
|
+
for (const t of db.all("SELECT * FROM trees")) {
|
|
4849
|
+
const nodes = db.all("SELECT * FROM nodes WHERE tree_id=?", [t.id]);
|
|
4850
|
+
let c = `---
|
|
4851
|
+
type: tree
|
|
4852
|
+
---
|
|
4853
|
+
|
|
4854
|
+
# ${t.name} (${nodes.length} nodes)
|
|
4855
|
+
|
|
4856
|
+
`;
|
|
4857
|
+
for (const n of nodes) c += `- **${n.key}** (${n.kind}, tier ${n.tier})
|
|
4858
|
+
`;
|
|
4859
|
+
node_fs.writeFileSync(node_path.join(outDir, "trees", safe(t.name) + ".md"), c);
|
|
4860
|
+
count++;
|
|
4861
|
+
}
|
|
4862
|
+
for (const cl of db.all("SELECT * FROM claims")) {
|
|
4863
|
+
const links = db.all("SELECT e.quote FROM claim_evidence ce JOIN evidence e ON ce.evidence_id=e.id WHERE ce.claim_id=?", [cl.id]);
|
|
4864
|
+
let c2 = `---
|
|
4865
|
+
type: claim
|
|
4866
|
+
status: ${cl.status}
|
|
4867
|
+
confidence: ${cl.confidence}
|
|
4868
|
+
---
|
|
4869
|
+
|
|
4870
|
+
# ${cl.id}
|
|
4871
|
+
|
|
4872
|
+
${cl.text}
|
|
4873
|
+
|
|
4874
|
+
## Evidence
|
|
4875
|
+
`;
|
|
4876
|
+
for (const l of links) c2 += `- > ${String(l.quote).slice(0, 100)}
|
|
4877
|
+
`;
|
|
4878
|
+
node_fs.writeFileSync(node_path.join(outDir, "claims", cl.id + ".md"), c2);
|
|
4879
|
+
count++;
|
|
4880
|
+
}
|
|
4881
|
+
ok(ctx, `obsidian vault exported: ${count} files → ${outDir}`);
|
|
4882
|
+
return 0;
|
|
4883
|
+
}
|
|
4671
4884
|
case "export": {
|
|
4672
4885
|
const dest = rest[0] || `ygl-trees-dump-${Date.now()}.sql`;
|
|
4673
4886
|
const skip = args.flags.lean === true || args.flags.lean === "true" ? /* @__PURE__ */ new Set(["node_closure", "node_index"]) : /* @__PURE__ */ new Set();
|
|
@@ -5291,7 +5504,10 @@ function buildBrief(env) {
|
|
|
5291
5504
|
const { store } = env;
|
|
5292
5505
|
const cur = store.db.metaGet("current_project");
|
|
5293
5506
|
const stats = store.stats();
|
|
5294
|
-
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
|
+
}));
|
|
5295
5511
|
const notes = store.kbList().slice(0, 5);
|
|
5296
5512
|
const payload = {
|
|
5297
5513
|
workspace: env.home,
|
|
@@ -5351,6 +5567,101 @@ function buildBrief(env) {
|
|
|
5351
5567
|
function run$9(env, action, rest, args) {
|
|
5352
5568
|
const { ctx, store } = env;
|
|
5353
5569
|
switch (action) {
|
|
5570
|
+
case "ask":
|
|
5571
|
+
case "?": {
|
|
5572
|
+
const question = rest.join(" ");
|
|
5573
|
+
if (!question) {
|
|
5574
|
+
bad(ctx, "usage: ygl-trees agent ask \"<question>\"");
|
|
5575
|
+
out(ctx, " examples: ask \"what projects\" | ask \"search AI\" | ask \"健康\"");
|
|
5576
|
+
return 2;
|
|
5577
|
+
}
|
|
5578
|
+
const q = question.toLowerCase();
|
|
5579
|
+
const routes = [
|
|
5580
|
+
{
|
|
5581
|
+
pattern: /what.*project|项目列表|所有项目/,
|
|
5582
|
+
label: "project list",
|
|
5583
|
+
group: "project",
|
|
5584
|
+
action: "list"
|
|
5585
|
+
},
|
|
5586
|
+
{
|
|
5587
|
+
pattern: /search|搜索|检索|找/,
|
|
5588
|
+
label: "kb search",
|
|
5589
|
+
group: "kb",
|
|
5590
|
+
action: "search",
|
|
5591
|
+
extra: [question.replace(/.*?(search|搜索|检索|找)\s*/i, "").replace(/["']/g, "")]
|
|
5592
|
+
},
|
|
5593
|
+
{
|
|
5594
|
+
pattern: /tree|树/,
|
|
5595
|
+
label: "node list",
|
|
5596
|
+
group: "node",
|
|
5597
|
+
action: "list"
|
|
5598
|
+
},
|
|
5599
|
+
{
|
|
5600
|
+
pattern: /health|健康|doctor|检查/,
|
|
5601
|
+
label: "doctor",
|
|
5602
|
+
group: "doctor",
|
|
5603
|
+
action: ""
|
|
5604
|
+
},
|
|
5605
|
+
{
|
|
5606
|
+
pattern: /skill|技能/,
|
|
5607
|
+
label: "skill list",
|
|
5608
|
+
group: "skill",
|
|
5609
|
+
action: "list"
|
|
5610
|
+
},
|
|
5611
|
+
{
|
|
5612
|
+
pattern: /task|任务/,
|
|
5613
|
+
label: "task list",
|
|
5614
|
+
group: "task",
|
|
5615
|
+
action: "list"
|
|
5616
|
+
},
|
|
5617
|
+
{
|
|
5618
|
+
pattern: /metric|指标/,
|
|
5619
|
+
label: "agent metrics",
|
|
5620
|
+
group: "agent",
|
|
5621
|
+
action: "metrics"
|
|
5622
|
+
}
|
|
5623
|
+
];
|
|
5624
|
+
for (const r of routes) if (r.pattern.test(q)) {
|
|
5625
|
+
if (!ctx.json) out(ctx, paint(C.dim, ` → ${r.label}`, ctx.color));
|
|
5626
|
+
const cmdParts = [
|
|
5627
|
+
r.group,
|
|
5628
|
+
r.action,
|
|
5629
|
+
...r.extra || []
|
|
5630
|
+
].filter(Boolean);
|
|
5631
|
+
const { execSync } = require("node:child_process");
|
|
5632
|
+
const self = process.argv[1];
|
|
5633
|
+
try {
|
|
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
|
+
},
|
|
5639
|
+
encoding: "utf8",
|
|
5640
|
+
timeout: 1e4
|
|
5641
|
+
});
|
|
5642
|
+
if (ctx.json) {
|
|
5643
|
+
console.log(result.trim());
|
|
5644
|
+
return 0;
|
|
5645
|
+
}
|
|
5646
|
+
out(ctx, result.trim());
|
|
5647
|
+
return 0;
|
|
5648
|
+
} catch (e) {
|
|
5649
|
+
if (ctx.json) {
|
|
5650
|
+
console.log(JSON.stringify({ error: String(e?.message || e).slice(0, 200) }));
|
|
5651
|
+
return 1;
|
|
5652
|
+
}
|
|
5653
|
+
bad(ctx, String(e?.stdout || e?.message || e).slice(0, 200));
|
|
5654
|
+
return 1;
|
|
5655
|
+
}
|
|
5656
|
+
}
|
|
5657
|
+
if (!ctx.json) out(ctx, paint(C.dim, " → agent brief (no specific match)", ctx.color));
|
|
5658
|
+
const payload = buildBrief(env);
|
|
5659
|
+
if (ctx.json) {
|
|
5660
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
5661
|
+
return 0;
|
|
5662
|
+
}
|
|
5663
|
+
return run$9(env, "brief", [], args);
|
|
5664
|
+
}
|
|
5354
5665
|
case "metrics": {
|
|
5355
5666
|
const t0 = Date.now();
|
|
5356
5667
|
const brief = buildBrief(env);
|
|
@@ -5421,7 +5732,10 @@ function run$9(env, action, rest, args) {
|
|
|
5421
5732
|
const payload = buildBrief(env);
|
|
5422
5733
|
const cur = payload.current_project;
|
|
5423
5734
|
const stats = payload.counts;
|
|
5424
|
-
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
|
+
}));
|
|
5425
5739
|
if (ctx.json) {
|
|
5426
5740
|
console.log(JSON.stringify(payload, null, 2));
|
|
5427
5741
|
return 0;
|
|
@@ -8216,6 +8530,70 @@ function run$4(env, action, rest, args) {
|
|
|
8216
8530
|
return 1;
|
|
8217
8531
|
});
|
|
8218
8532
|
}
|
|
8533
|
+
case "init": {
|
|
8534
|
+
const name = String(args.flags.name || rest[0] || "");
|
|
8535
|
+
if (!name) {
|
|
8536
|
+
bad(ctx, "usage: skill init <name> [--description ...] [--body ...]");
|
|
8537
|
+
bad(ctx, " creates a skill with frontmatter template; edit body with skill show + update");
|
|
8538
|
+
return 2;
|
|
8539
|
+
}
|
|
8540
|
+
const desc = String(args.flags.description || "");
|
|
8541
|
+
const body = String(args.flags.body || `# ${name}
|
|
8542
|
+
|
|
8543
|
+
## When to use
|
|
8544
|
+
(describe when this skill should activate)
|
|
8545
|
+
|
|
8546
|
+
## What to do
|
|
8547
|
+
(step-by-step instructions)
|
|
8548
|
+
|
|
8549
|
+
## Examples
|
|
8550
|
+
(concrete command examples)
|
|
8551
|
+
`);
|
|
8552
|
+
const tags = String(args.flags.tags || "");
|
|
8553
|
+
return getOrm(env.db.path).then((ds) => skillUpsert(ds, {
|
|
8554
|
+
name,
|
|
8555
|
+
description: desc,
|
|
8556
|
+
body,
|
|
8557
|
+
tags,
|
|
8558
|
+
version: "0.1.0"
|
|
8559
|
+
})).then((r) => {
|
|
8560
|
+
ok(ctx, `skill created: ${name} v0.1.0`);
|
|
8561
|
+
out(ctx, paint(C.dim, ` edit: ygl-trees skill show ${name}`, ctx.color));
|
|
8562
|
+
out(ctx, paint(C.dim, ` add refs: ygl-trees skill refs add ${name} <path> --body "..."`, ctx.color));
|
|
8563
|
+
return 0;
|
|
8564
|
+
}).catch((e) => {
|
|
8565
|
+
bad(ctx, String(e?.message || e));
|
|
8566
|
+
return 1;
|
|
8567
|
+
});
|
|
8568
|
+
}
|
|
8569
|
+
case "refs":
|
|
8570
|
+
case "references": {
|
|
8571
|
+
const skillName = rest[0];
|
|
8572
|
+
if (!skillName) {
|
|
8573
|
+
bad(ctx, "usage: skill refs <name>");
|
|
8574
|
+
return 2;
|
|
8575
|
+
}
|
|
8576
|
+
const sk = store.skillGet(skillName);
|
|
8577
|
+
if (!sk) {
|
|
8578
|
+
bad(ctx, `skill not found: ${skillName}`);
|
|
8579
|
+
return 1;
|
|
8580
|
+
}
|
|
8581
|
+
const refs = env.db.all("SELECT * FROM skill_refs WHERE skill_id=? ORDER BY path", [sk.id]);
|
|
8582
|
+
head(ctx, `Skill: ${skillName}`);
|
|
8583
|
+
if (ctx.json) {
|
|
8584
|
+
console.log(JSON.stringify(refs, null, 2));
|
|
8585
|
+
return 0;
|
|
8586
|
+
}
|
|
8587
|
+
if (!refs.length) {
|
|
8588
|
+
out(ctx, paint(C.dim, " (no references — add with: skill refs add <name> <path> --body ...)", ctx.color));
|
|
8589
|
+
return 0;
|
|
8590
|
+
}
|
|
8591
|
+
for (const r of refs) {
|
|
8592
|
+
out(ctx, ` ${r.path}`);
|
|
8593
|
+
if (r.body) out(ctx, paint(C.dim, ` ${String(r.body).slice(0, 80)}`, ctx.color));
|
|
8594
|
+
}
|
|
8595
|
+
return 0;
|
|
8596
|
+
}
|
|
8219
8597
|
case "add": {
|
|
8220
8598
|
const n = args.flags.name ? String(args.flags.name) : "";
|
|
8221
8599
|
const file = args.flags.f ? String(args.flags.f) : "";
|
|
@@ -8560,7 +8938,7 @@ function run$3(env, action, rest, args) {
|
|
|
8560
8938
|
"confidence",
|
|
8561
8939
|
"reuse",
|
|
8562
8940
|
"updated"
|
|
8563
|
-
]);
|
|
8941
|
+
], "kb add --title ... --body ... | kb import <dir>");
|
|
8564
8942
|
return 0;
|
|
8565
8943
|
case "rm":
|
|
8566
8944
|
case "remove": {
|
|
@@ -10441,7 +10819,49 @@ async function main(argv) {
|
|
|
10441
10819
|
out(ctx, appVersion());
|
|
10442
10820
|
return 0;
|
|
10443
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
|
+
}
|
|
10444
10838
|
if (group === "llms") {
|
|
10839
|
+
const section = String(args.flags.section || "");
|
|
10840
|
+
if (args.flags.json === true) {
|
|
10841
|
+
const manifest = generateLlmsManifest();
|
|
10842
|
+
const sections = {};
|
|
10843
|
+
let current = "header";
|
|
10844
|
+
for (const line of manifest.split("\n")) if (line.startsWith("## ")) {
|
|
10845
|
+
current = line.slice(3).toLowerCase().replace(/\s+/g, "_");
|
|
10846
|
+
sections[current] = line + "\n";
|
|
10847
|
+
} else if (sections[current] !== void 0) sections[current] += line + "\n";
|
|
10848
|
+
console.log(JSON.stringify({
|
|
10849
|
+
version: appVersion(),
|
|
10850
|
+
sections
|
|
10851
|
+
}, null, 2));
|
|
10852
|
+
return 0;
|
|
10853
|
+
}
|
|
10854
|
+
if (section) {
|
|
10855
|
+
const all = generateLlmsManifest().split("\n## ");
|
|
10856
|
+
const match = all.find((s2) => s2.toLowerCase().startsWith(section.toLowerCase()));
|
|
10857
|
+
if (match) {
|
|
10858
|
+
out(ctx, "## " + match);
|
|
10859
|
+
return 0;
|
|
10860
|
+
}
|
|
10861
|
+
bad(ctx, `section not found: ${section}`);
|
|
10862
|
+
out(ctx, ` available: ${all.map((s3) => s3.split("\n")[0]).join(" | ")}`);
|
|
10863
|
+
return 1;
|
|
10864
|
+
}
|
|
10445
10865
|
out(ctx, generateLlmsManifest());
|
|
10446
10866
|
return 0;
|
|
10447
10867
|
}
|
|
@@ -10579,7 +10999,72 @@ async function main(argv) {
|
|
|
10579
10999
|
}
|
|
10580
11000
|
const mod = DOMAIN_MODULES[group];
|
|
10581
11001
|
if (!mod) {
|
|
10582
|
-
|
|
11002
|
+
const known = [
|
|
11003
|
+
"agent",
|
|
11004
|
+
"research",
|
|
11005
|
+
"produce",
|
|
11006
|
+
"kb",
|
|
11007
|
+
"skill",
|
|
11008
|
+
"tree",
|
|
11009
|
+
"node",
|
|
11010
|
+
"task",
|
|
11011
|
+
"rel",
|
|
11012
|
+
"plugin",
|
|
11013
|
+
"webui",
|
|
11014
|
+
"db",
|
|
11015
|
+
"doctor",
|
|
11016
|
+
"config",
|
|
11017
|
+
"job",
|
|
11018
|
+
"project",
|
|
11019
|
+
"mcp",
|
|
11020
|
+
"llms",
|
|
11021
|
+
"version",
|
|
11022
|
+
"help"
|
|
11023
|
+
];
|
|
11024
|
+
const aliases = {
|
|
11025
|
+
database: "db",
|
|
11026
|
+
data: "db",
|
|
11027
|
+
sqlite: "db",
|
|
11028
|
+
knowledge: "kb",
|
|
11029
|
+
notes: "kb",
|
|
11030
|
+
notebook: "kb",
|
|
11031
|
+
helth: "doctor",
|
|
11032
|
+
check: "doctor",
|
|
11033
|
+
health: "doctor",
|
|
11034
|
+
server: "webui",
|
|
11035
|
+
dashboard: "webui",
|
|
11036
|
+
ui: "webui",
|
|
11037
|
+
timeline: "db",
|
|
11038
|
+
"time-travel": "db",
|
|
11039
|
+
workspace: "db",
|
|
11040
|
+
obsidian: "db"
|
|
11041
|
+
};
|
|
11042
|
+
const input = String(group).toLowerCase();
|
|
11043
|
+
const suggestions = [];
|
|
11044
|
+
if (aliases[input]) suggestions.push(aliases[input]);
|
|
11045
|
+
for (const [alias, cmd] of Object.entries(aliases)) if (alias.startsWith(input.slice(0, 4)) && !suggestions.includes(cmd)) {
|
|
11046
|
+
suggestions.push(cmd);
|
|
11047
|
+
break;
|
|
11048
|
+
}
|
|
11049
|
+
for (const k of known) if (k.startsWith(input.slice(0, 3)) && !suggestions.includes(k)) suggestions.push(k);
|
|
11050
|
+
if (suggestions.length < 2) for (const k of known) {
|
|
11051
|
+
if (suggestions.includes(k)) continue;
|
|
11052
|
+
let ki = 0;
|
|
11053
|
+
for (const ch of input) {
|
|
11054
|
+
ki = k.indexOf(ch, ki);
|
|
11055
|
+
if (ki === -1) break;
|
|
11056
|
+
ki++;
|
|
11057
|
+
}
|
|
11058
|
+
if (ki !== -1) suggestions.push(k);
|
|
11059
|
+
if (suggestions.length >= 3) break;
|
|
11060
|
+
}
|
|
11061
|
+
if (suggestions.length < 2) {
|
|
11062
|
+
for (const k of known) if (k.includes(input) && !suggestions.includes(k)) suggestions.push(k);
|
|
11063
|
+
}
|
|
11064
|
+
if (suggestions.length === 1) bad(ctx, `unknown group: ${group} — did you mean "${suggestions[0]}"?`);
|
|
11065
|
+
else if (suggestions.length > 1) bad(ctx, `unknown group: ${group} — did you mean: ${suggestions.slice(0, 3).join(" | ")}?`);
|
|
11066
|
+
else bad(ctx, `unknown group: ${group}`);
|
|
11067
|
+
out(ctx, paint(C.dim, " try: ygl-trees (no args) for full usage", ctx.color));
|
|
10583
11068
|
if (!ctx.json) out(ctx, usage());
|
|
10584
11069
|
return 2;
|
|
10585
11070
|
}
|
|
@@ -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
|
|
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)
|
|
@@ -82,6 +82,70 @@ function run(env, action, rest, args) {
|
|
|
82
82
|
return 1;
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
|
+
case "init": {
|
|
86
|
+
// v2: create a new skill from template — guides the author
|
|
87
|
+
const name = String(args.flags.name || rest[0] || "");
|
|
88
|
+
if (!name) {
|
|
89
|
+
(0, fmt_1.bad)(ctx, "usage: skill init <name> [--description ...] [--body ...]");
|
|
90
|
+
(0, fmt_1.bad)(ctx, " creates a skill with frontmatter template; edit body with skill show + update");
|
|
91
|
+
return 2;
|
|
92
|
+
}
|
|
93
|
+
const desc = String(args.flags.description || "");
|
|
94
|
+
const body = String(args.flags.body || `# ${name}
|
|
95
|
+
|
|
96
|
+
## When to use
|
|
97
|
+
(describe when this skill should activate)
|
|
98
|
+
|
|
99
|
+
## What to do
|
|
100
|
+
(step-by-step instructions)
|
|
101
|
+
|
|
102
|
+
## Examples
|
|
103
|
+
(concrete command examples)
|
|
104
|
+
`);
|
|
105
|
+
const tags = String(args.flags.tags || "");
|
|
106
|
+
return (0, orm_1.getOrm)(env.db.path)
|
|
107
|
+
.then((ds) => writes.skillUpsert(ds, { name, description: desc, body, tags, version: "0.1.0" }))
|
|
108
|
+
.then((r) => {
|
|
109
|
+
(0, fmt_1.ok)(ctx, `skill created: ${name} v0.1.0`);
|
|
110
|
+
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, ` edit: ygl-trees skill show ${name}`, ctx.color));
|
|
111
|
+
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, ` add refs: ygl-trees skill refs add ${name} <path> --body "..."`, ctx.color));
|
|
112
|
+
return 0;
|
|
113
|
+
})
|
|
114
|
+
.catch((e) => {
|
|
115
|
+
(0, fmt_1.bad)(ctx, String(e?.message || e));
|
|
116
|
+
return 1;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
case "refs":
|
|
120
|
+
case "references": {
|
|
121
|
+
// v2: show all references linked to a skill (L3 progressive disclosure)
|
|
122
|
+
const skillName = rest[0];
|
|
123
|
+
if (!skillName) {
|
|
124
|
+
(0, fmt_1.bad)(ctx, "usage: skill refs <name>");
|
|
125
|
+
return 2;
|
|
126
|
+
}
|
|
127
|
+
const sk = store.skillGet(skillName);
|
|
128
|
+
if (!sk) {
|
|
129
|
+
(0, fmt_1.bad)(ctx, `skill not found: ${skillName}`);
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
const refs = env.db.all("SELECT * FROM skill_refs WHERE skill_id=? ORDER BY path", [sk.id]);
|
|
133
|
+
(0, fmt_1.head)(ctx, `Skill: ${skillName}`);
|
|
134
|
+
if (ctx.json) {
|
|
135
|
+
console.log(JSON.stringify(refs, null, 2));
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
if (!refs.length) {
|
|
139
|
+
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, " (no references — add with: skill refs add <name> <path> --body ...)", ctx.color));
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
for (const r of refs) {
|
|
143
|
+
(0, fmt_1.out)(ctx, ` ${r.path}`);
|
|
144
|
+
if (r.body)
|
|
145
|
+
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, ` ${String(r.body).slice(0, 80)}`, ctx.color));
|
|
146
|
+
}
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
85
149
|
case "add": {
|
|
86
150
|
const n = args.flags.name ? String(args.flags.name) : "";
|
|
87
151
|
const file = args.flags.f ? String(args.flags.f) : "";
|
|
@@ -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,
|
|
@@ -74,6 +74,68 @@ function buildBrief(env) {
|
|
|
74
74
|
function run(env, action, rest, args) {
|
|
75
75
|
const { ctx, store } = env;
|
|
76
76
|
switch (action) {
|
|
77
|
+
case "ask":
|
|
78
|
+
case "?": {
|
|
79
|
+
// v2: natural language interface — keyword routing to other commands
|
|
80
|
+
const question = rest.join(" ");
|
|
81
|
+
if (!question) {
|
|
82
|
+
(0, fmt_1.bad)(ctx, 'usage: ygl-trees agent ask "<question>"');
|
|
83
|
+
(0, fmt_1.out)(ctx, ' examples: ask "what projects" | ask "search AI" | ask "健康"');
|
|
84
|
+
return 2;
|
|
85
|
+
}
|
|
86
|
+
const q = question.toLowerCase();
|
|
87
|
+
const routes = [
|
|
88
|
+
{ pattern: /what.*project|项目列表|所有项目/, label: "project list", group: "project", action: "list" },
|
|
89
|
+
{ pattern: /search|搜索|检索|找/, label: "kb search", group: "kb", action: "search",
|
|
90
|
+
extra: [question.replace(/.*?(search|搜索|检索|找)\s*/i, "").replace(/["']/g, "")] },
|
|
91
|
+
{ pattern: /tree|树/, label: "node list", group: "node", action: "list" },
|
|
92
|
+
{ pattern: /health|健康|doctor|检查/, label: "doctor", group: "doctor", action: "" },
|
|
93
|
+
{ pattern: /skill|技能/, label: "skill list", group: "skill", action: "list" },
|
|
94
|
+
{ pattern: /task|任务/, label: "task list", group: "task", action: "list" },
|
|
95
|
+
{ pattern: /metric|指标/, label: "agent metrics", group: "agent", action: "metrics" },
|
|
96
|
+
];
|
|
97
|
+
let matched = false;
|
|
98
|
+
for (const r of routes) {
|
|
99
|
+
if (r.pattern.test(q)) {
|
|
100
|
+
if (!ctx.json)
|
|
101
|
+
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, ` → ${r.label}`, ctx.color));
|
|
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).
|
|
105
|
+
const cmdParts = [r.group, r.action, ...(r.extra || [])].filter(Boolean);
|
|
106
|
+
const { execSync } = require("node:child_process");
|
|
107
|
+
const self = process.argv[1];
|
|
108
|
+
try {
|
|
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
|
+
}
|
|
115
|
+
(0, fmt_1.out)(ctx, result.trim());
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
if (ctx.json) {
|
|
120
|
+
console.log(JSON.stringify({ error: String(e?.message || e).slice(0, 200) }));
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
(0, fmt_1.bad)(ctx, String(e?.stdout || e?.message || e).slice(0, 200));
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Default: brief
|
|
129
|
+
if (!ctx.json)
|
|
130
|
+
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, " → agent brief (no specific match)", ctx.color));
|
|
131
|
+
const payload = buildBrief(env);
|
|
132
|
+
if (ctx.json) {
|
|
133
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
// Reuse brief rendering
|
|
137
|
+
return run(env, "brief", [], args);
|
|
138
|
+
}
|
|
77
139
|
case "metrics": {
|
|
78
140
|
// R11 cold-start research → automated measurement. Health/efficiency
|
|
79
141
|
// metrics for the workspace — one command answers "how good is this
|
|
@@ -150,7 +212,7 @@ function run(env, action, rest, args) {
|
|
|
150
212
|
const payload = buildBrief(env);
|
|
151
213
|
const cur = payload.current_project;
|
|
152
214
|
const stats = payload.counts;
|
|
153
|
-
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 ? "…" : "") }));
|
|
154
216
|
if (ctx.json) {
|
|
155
217
|
console.log(JSON.stringify(payload, null, 2));
|
|
156
218
|
return 0;
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.run = run;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
37
38
|
const fs = __importStar(require("node:fs"));
|
|
38
39
|
const fmt_1 = require("../../../core/text/src/fmt");
|
|
39
40
|
const db_1 = require("../../../core/database/src/db");
|
|
@@ -132,6 +133,215 @@ function run(env, action, rest, args) {
|
|
|
132
133
|
(0, fmt_1.show)(ctx, { file, size_kb: (fs.statSync(file).size / 1024).toFixed(1), ...s });
|
|
133
134
|
return 0;
|
|
134
135
|
}
|
|
136
|
+
case "time-travel": {
|
|
137
|
+
// v2: query workspace state at a past moment using entity created_at
|
|
138
|
+
const when = rest[0];
|
|
139
|
+
if (!when) {
|
|
140
|
+
(0, fmt_1.bad)(ctx, 'usage: db time-travel "YYYY-MM-DD HH:MM" (or "YYYY-MM-DD")');
|
|
141
|
+
return 2;
|
|
142
|
+
}
|
|
143
|
+
const ts = when.includes(" ") ? when : when + " 23:59:59";
|
|
144
|
+
const tables = ["projects", "sources", "evidence", "claims", "kb_notes", "skills", "trees", "nodes", "artifacts", "events"];
|
|
145
|
+
const counts = {};
|
|
146
|
+
for (const t of tables) {
|
|
147
|
+
try {
|
|
148
|
+
const r = db.get(`SELECT COUNT(*) as n FROM ${t} WHERE created <= ?`, [ts]);
|
|
149
|
+
counts[t] = r?.n || 0;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
counts[t] = 0;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// node unlock state at that time (via events)
|
|
156
|
+
let unlockedAt = 0;
|
|
157
|
+
try {
|
|
158
|
+
const r = db.get(`SELECT COUNT(DISTINCT entity_id) as n FROM events WHERE kind='node.unlock' AND ts <= ?`, [ts]);
|
|
159
|
+
unlockedAt = r?.n || 0;
|
|
160
|
+
}
|
|
161
|
+
catch { /* */ }
|
|
162
|
+
if (ctx.json) {
|
|
163
|
+
console.log(JSON.stringify({ timestamp: ts, counts, unlocked_nodes: unlockedAt }, null, 2));
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
(0, fmt_1.head)(ctx, `Workspace at ${ts}`);
|
|
167
|
+
const labels = {
|
|
168
|
+
projects: "项目", sources: "来源", evidence: "证据", claims: "断言",
|
|
169
|
+
kb_notes: "知识", skills: "技能", trees: "树", nodes: "节点",
|
|
170
|
+
artifacts: "产出", events: "事件",
|
|
171
|
+
};
|
|
172
|
+
for (const [k, v] of Object.entries(counts)) {
|
|
173
|
+
(0, fmt_1.out)(ctx, ` ${String(labels[k] || k).padEnd(6)} ${String(v).padStart(5)}`);
|
|
174
|
+
}
|
|
175
|
+
(0, fmt_1.out)(ctx, ` 已解锁 ${String(unlockedAt).padStart(5)}`);
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
case "timeline": {
|
|
179
|
+
// v2: workspace growth timeline — events per day with entity milestones
|
|
180
|
+
const days = db.all(`
|
|
181
|
+
SELECT date(ts) as day, COUNT(*) as events,
|
|
182
|
+
SUM(CASE WHEN kind LIKE 'project%' THEN 1 ELSE 0 END) as projects,
|
|
183
|
+
SUM(CASE WHEN kind LIKE 'source%' THEN 1 ELSE 0 END) as sources,
|
|
184
|
+
SUM(CASE WHEN kind LIKE 'claim%' THEN 1 ELSE 0 END) as claims,
|
|
185
|
+
SUM(CASE WHEN kind LIKE 'kb%' THEN 1 ELSE 0 END) as kb,
|
|
186
|
+
SUM(CASE WHEN kind LIKE 'node%' THEN 1 ELSE 0 END) as nodes
|
|
187
|
+
FROM events GROUP BY date(ts) ORDER BY day
|
|
188
|
+
`);
|
|
189
|
+
if (!days.length) {
|
|
190
|
+
(0, fmt_1.ok)(ctx, "no events yet");
|
|
191
|
+
return 0;
|
|
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
|
+
}
|
|
198
|
+
const maxEvents = Math.max(...days.map(d => d.events));
|
|
199
|
+
(0, fmt_1.head)(ctx, "Workspace Timeline");
|
|
200
|
+
let totalP = 0, totalS = 0, totalC = 0, totalK = 0, totalN = 0;
|
|
201
|
+
for (const d of days) {
|
|
202
|
+
totalP += d.projects;
|
|
203
|
+
totalS += d.sources;
|
|
204
|
+
totalC += d.claims;
|
|
205
|
+
totalK += d.kb;
|
|
206
|
+
totalN += d.nodes;
|
|
207
|
+
const barLen = Math.round(d.events / maxEvents * 30);
|
|
208
|
+
const bar = "█".repeat(barLen);
|
|
209
|
+
const milestones = [];
|
|
210
|
+
if (d.projects > 0)
|
|
211
|
+
milestones.push(`+${d.projects}项目`);
|
|
212
|
+
if (d.sources > 0)
|
|
213
|
+
milestones.push(`+${d.sources}来源`);
|
|
214
|
+
if (d.claims > 0)
|
|
215
|
+
milestones.push(`+${d.claims}断言`);
|
|
216
|
+
if (d.kb > 0)
|
|
217
|
+
milestones.push(`+${d.kb}知识`);
|
|
218
|
+
if (d.nodes > 0)
|
|
219
|
+
milestones.push(`+${d.nodes}节点`);
|
|
220
|
+
(0, fmt_1.out)(ctx, ` ${d.day} ${bar.padEnd(32)} ${String(d.events).padStart(5)} events ${milestones.join(" ")}`);
|
|
221
|
+
}
|
|
222
|
+
(0, fmt_1.out)(ctx, "");
|
|
223
|
+
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, ` 累计: ${totalP}项目 · ${totalS}来源 · ${totalC}断言 · ${totalK}知识 · ${totalN}节点变更`, ctx.color));
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
case "workspace": {
|
|
227
|
+
// v2: multi-workspace management — each workspace is a YGL_HOME directory
|
|
228
|
+
const sub = rest[0] || "list";
|
|
229
|
+
const wsRoot = path.join(env.home, ".."); // parent of current workspace
|
|
230
|
+
if (sub === "list") {
|
|
231
|
+
const dirs = fs.existsSync(wsRoot) ? fs.readdirSync(wsRoot).filter((d) => fs.existsSync(path.join(wsRoot, d, "ygl-trees.db"))) : [];
|
|
232
|
+
const cur = path.basename(env.home);
|
|
233
|
+
(0, fmt_1.head)(ctx, "Workspaces (parent: " + wsRoot + ")");
|
|
234
|
+
for (const d of dirs) {
|
|
235
|
+
(0, fmt_1.out)(ctx, (d === cur ? " * " : " ") + d);
|
|
236
|
+
}
|
|
237
|
+
(0, fmt_1.out)(ctx, "");
|
|
238
|
+
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, " * = current | switch: YGL_HOME=<path> ygl-trees <cmd>", ctx.color));
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
if (sub === "create") {
|
|
242
|
+
const name = rest[1];
|
|
243
|
+
if (!name) {
|
|
244
|
+
(0, fmt_1.bad)(ctx, "usage: db workspace create <name>");
|
|
245
|
+
return 2;
|
|
246
|
+
}
|
|
247
|
+
const dir = path.join(wsRoot, name);
|
|
248
|
+
if (fs.existsSync(path.join(dir, "ygl-trees.db"))) {
|
|
249
|
+
(0, fmt_1.ok)(ctx, `workspace already exists: ${name}`);
|
|
250
|
+
return 0;
|
|
251
|
+
}
|
|
252
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
253
|
+
(0, fmt_1.ok)(ctx, `workspace created: ${dir}`);
|
|
254
|
+
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, ` init: YGL_HOME=${dir} ygl-trees db init`, ctx.color));
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
(0, fmt_1.bad)(ctx, "usage: db workspace list | create <name>");
|
|
258
|
+
return 2;
|
|
259
|
+
}
|
|
260
|
+
case "obsidian": {
|
|
261
|
+
// v2 feature: export workspace as Obsidian-compatible markdown vault
|
|
262
|
+
const outDir = rest[0] || path.join(env.home, "obsidian-export");
|
|
263
|
+
fs.mkdirSync(path.join(outDir, "projects"), { recursive: true });
|
|
264
|
+
fs.mkdirSync(path.join(outDir, "knowledge"), { recursive: true });
|
|
265
|
+
fs.mkdirSync(path.join(outDir, "skills"), { recursive: true });
|
|
266
|
+
fs.mkdirSync(path.join(outDir, "trees"), { recursive: true });
|
|
267
|
+
fs.mkdirSync(path.join(outDir, "claims"), { recursive: true });
|
|
268
|
+
let count = 0;
|
|
269
|
+
const safe = (t) => String(t).replace(/[\/:*?"<>|]/g, "_").slice(0, 50);
|
|
270
|
+
for (const p of db.all("SELECT * FROM projects")) {
|
|
271
|
+
fs.writeFileSync(path.join(outDir, "projects", safe(p.name) + ".md"), `---
|
|
272
|
+
type: project
|
|
273
|
+
id: ${p.id}
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
# ${p.name}
|
|
277
|
+
|
|
278
|
+
${p.question || ""}
|
|
279
|
+
`);
|
|
280
|
+
count++;
|
|
281
|
+
}
|
|
282
|
+
for (const n of db.all("SELECT * FROM kb_notes")) {
|
|
283
|
+
fs.writeFileSync(path.join(outDir, "knowledge", safe(n.title) + ".md"), `---
|
|
284
|
+
type: knowledge
|
|
285
|
+
tags: ${n.tags || ""}
|
|
286
|
+
confidence: ${n.confidence}
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
# ${n.title}
|
|
290
|
+
|
|
291
|
+
${n.body}
|
|
292
|
+
`);
|
|
293
|
+
count++;
|
|
294
|
+
}
|
|
295
|
+
for (const sk of db.all("SELECT * FROM skills WHERE enabled=1")) {
|
|
296
|
+
fs.writeFileSync(path.join(outDir, "skills", safe(sk.name) + ".md"), `---
|
|
297
|
+
type: skill
|
|
298
|
+
version: ${sk.version}
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
# ${sk.name}
|
|
302
|
+
|
|
303
|
+
${String(sk.body).slice(0, 500)}
|
|
304
|
+
`);
|
|
305
|
+
count++;
|
|
306
|
+
}
|
|
307
|
+
for (const t of db.all("SELECT * FROM trees")) {
|
|
308
|
+
const nodes = db.all("SELECT * FROM nodes WHERE tree_id=?", [t.id]);
|
|
309
|
+
let c = `---
|
|
310
|
+
type: tree
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
# ${t.name} (${nodes.length} nodes)
|
|
314
|
+
|
|
315
|
+
`;
|
|
316
|
+
for (const n of nodes)
|
|
317
|
+
c += `- **${n.key}** (${n.kind}, tier ${n.tier})
|
|
318
|
+
`;
|
|
319
|
+
fs.writeFileSync(path.join(outDir, "trees", safe(t.name) + ".md"), c);
|
|
320
|
+
count++;
|
|
321
|
+
}
|
|
322
|
+
for (const cl of db.all("SELECT * FROM claims")) {
|
|
323
|
+
const links = db.all("SELECT e.quote FROM claim_evidence ce JOIN evidence e ON ce.evidence_id=e.id WHERE ce.claim_id=?", [cl.id]);
|
|
324
|
+
let c2 = `---
|
|
325
|
+
type: claim
|
|
326
|
+
status: ${cl.status}
|
|
327
|
+
confidence: ${cl.confidence}
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
# ${cl.id}
|
|
331
|
+
|
|
332
|
+
${cl.text}
|
|
333
|
+
|
|
334
|
+
## Evidence
|
|
335
|
+
`;
|
|
336
|
+
for (const l of links)
|
|
337
|
+
c2 += `- > ${String(l.quote).slice(0, 100)}
|
|
338
|
+
`;
|
|
339
|
+
fs.writeFileSync(path.join(outDir, "claims", cl.id + ".md"), c2);
|
|
340
|
+
count++;
|
|
341
|
+
}
|
|
342
|
+
(0, fmt_1.ok)(ctx, `obsidian vault exported: ${count} files → ${outDir}`);
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
135
345
|
case "export": {
|
|
136
346
|
const dest = rest[0] || `ygl-trees-dump-${Date.now()}.sql`;
|
|
137
347
|
// --lean: skip derived-table data (node_closure / node_index are
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ygl-trees v0.
|
|
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.
|
|
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",
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@deepseek-ai/dsh-atomic-write": "0.0.1-rc.1",
|
|
23
23
|
"better-sqlite3": "^13.0.3",
|
|
24
|
-
"node-sqlite3-wasm": "^0.8.
|
|
24
|
+
"node-sqlite3-wasm": "^0.8.61",
|
|
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/cosmokit": "~1.8.6-alpha.2",
|
|
29
28
|
"@deepseek-ai/cordis-plugin-timer": "~1.1.7-alpha.2",
|
|
29
|
+
"@deepseek-ai/cosmokit": "~1.8.6-alpha.2",
|
|
30
30
|
"@deepseek-ai/schemastery": "~3.18.5-alpha.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|