ygl-trees 0.78.0 → 0.79.1

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,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.79.1(2026-10-17)锁身份 + serve --help 短路 + 批量判定 + 回测状态桥
4
+ - **异常①锁归属**:`.ygllock` 记录持有者命令行;撞锁报错、`db unlock`(含 `holder_cmd` JSON 字段)、`doctor` 三处回显真实持有者身份——CLI 撞 CLI 不再被误标为 serve(serve 从不取写锁,报错明示这一点)
5
+ - **异常②**:`webui serve --help` 短路打印用法即退出,不再直接启动服务(随机端口误触发实证)
6
+ - **异常③**:serve 写锁警告改为如实措辞(WAL 并发读写安全,批量写才建议停 serve)
7
+ - **回归修复**:无锁进程(serve / db unlock)退出不再误清并行会话的活锁(ownsLock 所有权守卫——自 serve 时代潜伏的 unconditional releaseLockFor);`db unlock` 改为无锁模式启动,自身诊断命令不再被诊断对象卡死,其"活 holder 拒绝/--force/死 pid 清理"三条路径首次真正可达
8
+ - **特性①** `node can` 批量判定 + `--state-file <json>`:多键一进程(探针冷启动 1s×N → 1s);状态袋两种形状——扁平计数共享 / 按节点 `{key: bag}` 各取各袋;多键 `--json` 返回数组;`ready --state-file` 同步支持(扁平)
9
+ - **特性②** `job add` 接探针实证可用(任意命令 + `--every N` 本就支持,README 补闸门探针模式)
10
+ - **特性③(m02806)** predict audit:已 `claim resolve` 的预测默认隐藏(回测终态 resolved),`--all` 查看全表;空表提示
11
+ - **特性④(m02801)** predict audit `--state <file|url>`:外部计数桥接(HTTP 3s 超时 / 本地文件),嵌套对象扁平化(`{counts:{y:12}}` → `counts.y=12`),判据裸键名后缀匹配(`y_imported≥12` ← `counts.y_imported`,实测列显示来源键);加载失败回落 manual 并警告
12
+ - 修复 `node can` 无 `--state-file` 时误报 usage rc=2 的回归(demo 门禁当场抓获——无标记路径此前未测)
13
+ - 单测 163(+7:state 桥接/后缀匹配/resolved 终态/loadStateSource)、demo 82(+8:5g 锁身份+批量+状态桥)、readme:verify 38(+4)
14
+
15
+ ## 0.79.0(2026-10-17)真实用户反馈硬化(四异常 + 六特性)
16
+ - **报告②新命令** `db unlock`:清残留 .ygllock——pid 存活检测,死 pid 直接清;活 holder(并行会话/serve)拒绝并解释,`--force` 才强清;自持锁(boot 期间获得)自清不死锁
17
+ - **报告②** doctor 锁检查升级 pid 存活判定:外进程活锁=PASS(多会话正常态),死 pid 才告警;`--fix` 只清死锁(此前会误删并行会话的活锁)
18
+ - **报告③** 选择变更事件化:`tree use`/`project use`/`tree new`/`project new`/`node import` 全落 `tree.use`/`project.use` 事件(带 `YGL_SESSION`);`agent brief` 新增 `selection_changed`(最近 10 分钟),跨会话变更置顶提示带 `-t`/`-p`
19
+ - **报告③** node not found 增强:key 在其他树 → 点名树并给 `-t` 提示;不存在 → 显示当前树名(并行会话翻转 current_tree 后错误自解释)
20
+ - **报告①** kb add 回执自检:提交后立即 kbGet 回读,`--json` 带 `verified:true` + `workspace`;人类模式回显 `@ <home>`(防写错 home 的"丢笔记");doctor 第 10 项 write_integrity:回放近 2 天 kb/source/evidence 写事件对账基表,幻影写入点名
21
+ - **报告④** claim link 三 id 纲目修复:误传来源 id → 列出该来源证据 id + 建议;无证据来源 → 提示先 evidence add;`--by-source` 批量挂齐来源下全部证据
22
+ - **报告⑤新命令** `node timeline <tree>`:单树成长时间线(add/link/unlock 事件倒序 30 条)
23
+ - **报告⑤** `research evidence add --file <path>`:长摘录免 shell 转义(与 --quote 互斥,空文件 rc=2)
24
+ - **确认①** produce spec 拒绝 `--design-type`(rc=2 并指向 produce design)
25
+ - **确认②** project new 回执提示前项目不再选中 + 回切命令
26
+ - 单测 156(+5:pidAlive/lockHolder 语义)、demo 74(+5:5f 反馈硬化断言)、contract 86
27
+
3
28
  ## 0.78.0(2026-10-17)预测回测管线
4
29
  - **新命令** `research predict audit`:扫描 [预测·N天] 断言——deadline 解析(显式日期优先,created+horizon 推导兜底)、本地可测判据自动实测(树/kb 计数,或语义 any-hit)、npm 版本数 registry 探测(3s 超时,--offline 关闭)
5
30
  - 判定四态:hit / miss / pending(含已达标标记)/ manual(外部判据)——只报告不仲裁,给出待 resolve 命令建议
package/README.md CHANGED
@@ -88,9 +88,11 @@ ygl-trees node link bronze steel --optional # 可选,不阻塞
88
88
  ygl-trees node link bronze iron --mode OR # 任一即可
89
89
  ygl-trees node link a b --rel excludes # 互斥分支
90
90
  ygl-trees node can steel --state "research_lab_level=3,has_engineer=1"
91
+ ygl-trees node can a b c --state-file probe.json # 批量判定:一进程测 N 节点(探针冷启动 1s×N → 1s)
91
92
  ygl-trees node ready # 当前可解锁集合
92
93
  ygl-trees node path advanced-alloy # 到目标的最省顺序 + 累计成本
93
94
  ygl-trees node ancestors bronze # 闭包索引,直接父 depth=1
95
+ ygl-trees node timeline demo # 单树成长时间线(add/link/unlock 倒序)
94
96
  ygl-trees node export --out tree.json # 可移植 JSON
95
97
  ygl-trees node import --file tree.json --tree copy # 严格校验、事务导入
96
98
  ```
@@ -99,6 +101,8 @@ ygl-trees node import --file tree.json --tree copy # 严格校验、事务导
99
101
 
100
102
  `node can` 的输出把三类失败分开列:**必检未满足 / 被排斥 / 可选未满足(不阻塞)**,外加成本系数。
101
103
 
104
+ **批量判定 + `--state-file`(0.79.1)**:`node can k1 k2 k3` 一次进程判定多节点(多键自动进批模式,`--json` 返回数组);`--state-file <json>` 喂状态袋,两种形状——**扁平计数** `{"y_imported": 40}` 全节点共享,或**按节点** `{"a": {"y_imported": 40}, "b": {...}}` 各取各袋。探针脚本从此每树起一次进程而非每节点。
105
+
102
106
  **决策模型评估门(spec R8;0.14 布尔 → 0.26 三题型 → 0.27 混批)**:语义型必检条件用 eval 题型声明,`node can <key> --eval` 会把**节点元数据 + 直接父(关联)+ 祖先链(闭包索引)+ 条件组 + 已解锁集 + --state 袋**组装为上下文包,交给评估模型(TypeSafe jev,经 Vercel AI Gateway)判定:
103
107
 
104
108
  - **三题型**:`--op eval`(布尔 P(true))/ `--op eval-choice`(多选一,判 **P(期望答案)**,`--options "k=desc,..."` + `--answer k`)/ `--op eval-score`(等级评分 `--levels "级0,..."` 2-10 级 + `--min-score n`,判概率加权均值 ±1 灰带)
@@ -121,11 +125,14 @@ query add 登记检索式
121
125
  query attach 检索式 ↔ 来源关联(query list 显示召回数)
122
126
  source add 收来源,按 (project, url_hash) 去重;--query 顺带关联
123
127
  source fetch <url> 直接抓取网页入库:title/meta description/去标签正文;15s 超时、非 html 拒收、失败零副作用,正文过薄自动打 needs_review
124
- evidence add 从来源摘原文片段,带 locator(页码/章节)
128
+ evidence add 从来源摘原文片段,带 locator(页码/章节);长摘录用 --file <path> 免转义
125
129
  claim add 写下断言
126
130
  claim link 断言 ↔ 证据,自动算 supported / refuted / contested
131
+ --by-source:传来源 id 一次挂齐其全部证据;误传来源 id 会列出该来源的证据 id
127
132
  claim resolve 人工仲裁出口:--as supported|refuted|contested,事件留痕;后续新证据链接会重新推导
128
133
  predict audit 预测回测:扫描 [预测·N天] 断言,本地可测判据自动测(树/kb 计数、npm 版本数),判 hit/miss/pending/manual——只报告不仲裁,给出待 resolve 建议
134
+ --state <file|url>:外部计数桥接(如 DSH API 快照),判据里的裸键名后缀匹配嵌套键(y_imported≥12 ← counts.y_imported)
135
+ 已 claim resolve 的预测默认隐藏(回测终态),--all 查看全表
129
136
  ```
130
137
 
131
138
  **JSON 纪律**:写命令在 `--json` 下 stdout 只有一个 JSON 对象(重复时带 `duplicate: true`),Agent 可直接 `JSON.parse` 全量输出。
@@ -251,6 +258,7 @@ CJK 检索是个坑:FTS5 默认 `unicode61` 会把整句中文当成一个 tok
251
258
 
252
259
  ```bash
253
260
  ygl-trees job add "nightly" -- "ygl-trees produce report" --every 1440
261
+ ygl-trees job add "闸门探针" -- "node ~/.local/lib/ygl-trees/tmp/dsh-tree-probe.mjs" --every 30 # 树自带节奏化监控(0.79.1 实证用法)
254
262
  ygl-trees job tick # cron 每分钟调它,只跑到期的任务
255
263
  ygl-trees job logs
256
264
  ```
@@ -273,7 +281,7 @@ ygl-trees agent ask "<question>" natural language interface
273
281
  ## 五b、开发与运维工具
274
282
 
275
283
  ```bash
276
- pnpm test # 四段制回归:unit (vitest 123 例) + build (tsc + tsdown) + demo(61 条断言)+ contract:audit(84 项)
284
+ pnpm test # 四段制回归:unit (vitest 163 例) + build (tsc + tsdown) + demo(82 条断言)+ contract:audit(86 项)
277
285
  pnpm bench # 性能基准七席(startup/写路径/检索/索引),每席含 RSS 内存端点 → tmp/bench-results.json
278
286
  node benchmarks/bench.js --quick # 缩小规模快速跑
279
287
  pnpm pack:check # 发布就绪门禁:打包 → 隔离安装 → 驱动发布产物跑命令面 + --json 纯净性
@@ -281,11 +289,24 @@ ygl-trees --version # 打印版本号
281
289
  ygl-trees llms # 结构化能力清单(LLM 参考一次性获取全部命令面)
282
290
  pnpm contract:audit # 写路径契约体检:写命令跑两次必须 rc=0、重复回执 duplicate、坏引用点名、用法 rc=2
283
291
  pnpm smoke:bun # Bun 运行时探针(Windows 当前受阻,见 docs/bun-compat.md)
284
- ygl-trees doctor # 工作区健康检查 9 项(--fix 一键安全修复)(schema/索引/FTS/重复/孤儿/config/锁/eval 信封/插件 profile)
292
+ ygl-trees doctor # 工作区健康检查 10 项(--fix 一键安全修复)(schema/索引/FTS/重复/孤儿/config/锁存活/eval 信封/插件 profile/幻影写入)
293
+ ygl-trees db unlock # 清残留 .ygllock:查 pid 存活,死 pid 直接清;活 holder(回显其命令行)拒绝并说明,--force 才强清
294
+ ygl-trees node timeline <tree> # 单树成长时间线(add/link/unlock 事件倒序)
285
295
  ygl-trees webui serve # React 富客户端(http://127.0.0.1:8642/,SSE 实时推送,Ctrl+C 停)
296
+ ygl-trees webui serve --help # 打印用法即退出,不启服务(0.79.1:--help 短路)
286
297
  ygl-trees webui export --out d.html # 零依赖单文件仪表盘(离线可开)
287
298
  ```
288
299
 
300
+ **锁身份(0.79.1)**:`.ygllock` 里除 pid 外记录**持有者命令行**;撞锁报错、`db unlock`、`doctor`
301
+ 三处都会回显真实持有者(如 `node bin.cjs node unlock trend-collector`),不再把 CLI 会话误标成
302
+ serve。serve 从不取写锁(WAL 并发读写安全),`db unlock` 也以无锁模式启动——自己的诊断命令
303
+ 不该被诊断对象卡住。
304
+
305
+ **多会话安全(0.79)**:`tree use` / `project use` 全部落 `tree.use` / `project.use` 事件(带
306
+ `YGL_SESSION` 会话标识);`agent brief` 的 `selection_changed` 字段暴露最近 10 分钟的选择变更,
307
+ 跨会话变更会置顶提示命令带 `-t/-p` 固定上下文。`kb add` 回执自带 `verified` 自检与工作区回显,
308
+ `doctor` 第 10 项回放近 2 天 kb/source/evidence 写事件对账基表,幻影写入当场点名。
309
+
289
310
  **提交门禁(无 CI 环境的等效方案)**:`.githooks/pre-push` —— push 分支跑 `pnpm test`,
290
311
  **push tag 追加 `pnpm pack:check`**;旁路只能显式 `YGL_SKIP_HOOKS=1`。安装一次:
291
312
  `git config core.hooksPath .githooks`。(本仓库 Gitee Go 服务对当前账户 403 不可用,见 [docs/release.md](docs/release.md)。)
@@ -157,10 +157,11 @@ ${(0, fmt_1.paint)(fmt_1.C.bold, "research", true)} collect → screen → ex
157
157
  source add --title --url [--abstract --content --relevance --confidence --query]
158
158
  source fetch <url> fetch page, extract title/meta/text (zero side-effect on failure)
159
159
  source list | search "<q>" | show <id> | rm <id>
160
- evidence add <sourceId> --quote "..." [--note --locator]
160
+ evidence add <sourceId> --quote "..." | --file <path> [--note --locator]
161
161
  evidence list
162
162
  claim add "<text>" [--confidence A|B|C]
163
163
  claim link <claimId> <evidenceId> [--stance supports|refutes]
164
+ --by-source <claimId> <sourceId>: link ALL evidence under a source
164
165
  claim resolve <claimId> --as supported|refuted|contested [--note]
165
166
  claim list
166
167
 
@@ -216,6 +217,7 @@ ${(0, fmt_1.paint)(fmt_1.C.bold, "tree / node", true)} tech-tree node model
216
217
  node validate cycles, dangling edges, dirty index
217
218
  node export [--out file.json] portable JSON
218
219
  node import --file <file.json> [--tree name] [--merge] — strict, transactional
220
+ node timeline [tree] per-tree growth history (add/link/unlock events)
219
221
 
220
222
  Writes are idempotent (claim/evidence/query/kb/job dedupe by content
221
223
  hash); pass --force to keep a deliberate duplicate.
@@ -273,6 +275,13 @@ async function main(argv) {
273
275
  if (group === "webui" && (action === "serve" || action === "export")) {
274
276
  process.env.YGL_SERVE = "1";
275
277
  }
278
+ // 0.79.1 (R297 user report follow-up): `db unlock` must be callable WHILE
279
+ // another process holds the lock — otherwise its live-holder refusal and
280
+ // --force paths are unreachable (boot would collide first). It only touches
281
+ // the lock dir on the filesystem, never the DB, so boot lock-free.
282
+ if (group === "db" && action === "unlock") {
283
+ process.env.YGL_SERVE = "1";
284
+ }
276
285
  // P4 fix: --version prints the actual version number (cold-user report);
277
286
  // the parser eats --version as a flag, so check flags not positional args.
278
287
  if (args.flags.version === true || group === "version") {
@@ -417,8 +426,18 @@ async function main(argv) {
417
426
  alive = err.code === "EPERM";
418
427
  }
419
428
  if (alive) {
420
- (0, fmt_1.out)(ctx, ` lock held by live process pid ${holder} (serve? webui dashboard)`);
421
- (0, fmt_1.out)(ctx, ` to run CLI write commands, stop that process first — or use YGL_TEST=1 to bypass`);
429
+ // 0.79.1 (R297 user report): the old hint guessed "(serve? webui
430
+ // dashboard)" — serve never takes the write lock, so the guess sent
431
+ // users to kill the wrong process. Echo the holder's self-reported
432
+ // command line from the lock dir instead.
433
+ let holderCmd = "";
434
+ try {
435
+ holderCmd = require("node:fs").readFileSync(require("node:path").join(lock, "cmd"), "utf8").trim();
436
+ }
437
+ catch { /* */ }
438
+ (0, fmt_1.out)(ctx, ` lock held by live process pid ${holder}${holderCmd ? `\n holder command: ${holderCmd}` : " (parallel CLI session — serve never takes the write lock)"}`);
439
+ (0, fmt_1.out)(ctx, ` reads still work; the lock serializes CLI writers and self-heals when pid ${holder} exits`);
440
+ (0, fmt_1.out)(ctx, ` force through (know what you are doing): YGL_TEST=1 · inspect/clear: ygl-trees db unlock`);
422
441
  }
423
442
  else {
424
443
  (0, fmt_1.out)(ctx, ` orphaned lock (pid ${holder} is dead) — removing: ${lock}`);
@@ -441,9 +460,16 @@ async function main(argv) {
441
460
  db?.close();
442
461
  }
443
462
  catch { /* best effort */ }
444
- // P0-2 belt-and-suspenders: force-release the lock on any exit path
463
+ // P0-2 belt-and-suspenders: force-release the lock on any exit path —
464
+ // but ONLY one this process owns. Unconditional release nuked FOREIGN
465
+ // locks whenever a lock-less process (serve; db unlock since 0.79.1)
466
+ // exited next to a live holder.
445
467
  try {
446
- require("../../../packages/core/database/src/db").DB.releaseLockFor(file);
468
+ const { DB } = require("../../../packages/core/database/src/db");
469
+ if (db?.ownsLock)
470
+ DB.releaseLockFor(file);
471
+ else if (DB.lockHolder(file) === String(process.pid))
472
+ DB.releaseLockFor(file);
447
473
  }
448
474
  catch { /* */ }
449
475
  });
@@ -700,10 +726,11 @@ function generateLlmsManifest() {
700
726
  ["db export <file>", "full backup"],
701
727
  ["db sync pg <url>", "one-way mirror to PostgreSQL"],
702
728
  ["db migrate pg <url> [--fresh]", "full-fidelity graduation (with type preflight)"],
729
+ ["db unlock [--force]", "clear stale .ygllock (pid-liveness checked; refuses live holders)"],
703
730
  ["db reset", "delete database"],
704
731
  ]],
705
- ["doctor", "Health check (9 items)", [
706
- ["doctor [--fix]", "diagnose (+ auto-fix: dirty index, stale lock)"],
732
+ ["doctor", "Health check (10 items)", [
733
+ ["doctor [--fix]", "diagnose (+ auto-fix: dirty index, dead stale lock, phantom writes)"],
707
734
  ]],
708
735
  ["config", "User settings", [
709
736
  ["config list / get / set / reset", "registry-backed settings"],