ygl-trees 0.82.0 → 0.83.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 +15 -0
- package/README.md +8 -2
- package/dist/apps/cli/src/bin.js +37 -0
- package/dist/bundle/bin.cjs +136 -14
- package/dist/packages/ops/tools/src/job-cmds.js +11 -1
- package/dist/packages/research/pipeline/src/cmds.js +66 -8
- package/dist/packages/research/pipeline/src/writes.js +32 -2
- package/dist/packages/research/produce/src/produce-cmds.js +2 -2
- package/dist/packages/research/produce/src/project-cmds.js +1 -1
- package/dist/packages/techtree/model/src/cmds.js +30 -5
- package/package.json +2 -2
- package/skills/ygl-trees-eval-gates/SKILL.md +2 -0
- package/skills/ygl-trees-quickstart/SKILL.md +5 -1
- package/skills/ygl-trees-research/SKILL.md +6 -2
- package/skills/ygl-trees-techtree/SKILL.md +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.83.0(2026-10-17)并发防护:指针钉住 + 半写拒绝 + 帮助面
|
|
4
|
+
- **写命令长旗标钉住**(用户上报①,最重:并发会话两次劫持共享 current-project 指针,资产碎片化跨 3 项目):`research *` / `produce *`(含 task)/ `project *` 全部接受 `--project <p>` 长旗标,`node *` 接受 `--tree <t>`(短旗标 `-p`/`-t` 不变;`node import --tree` 仍是目标树名,别名不遮蔽);source/evidence/claim/task 写回执带 `· project <名>` 回显落点——写错项目当场可见,不必事后对账
|
|
5
|
+
- **`source add --query` 半写防护**(上报①:attach 失败仍落源):引用不存在时**先拒绝后写入**(rc=1 点名、零落地),不再"源已落、链接失败";attach 落库后失败的路径改报已落 id + `research query attach` 重试指路
|
|
6
|
+
- **`--help` 任意深度**(上报②:`kb add --help` 不识别直接 rc=2):任意子命令带 `--help` 打印所在组的用法切片 rc=0(`--json` 返回 `{help}`),在 flag 校验与 db boot 之前短路;`webui serve --help` 等原有短路兼容
|
|
7
|
+
- **no-db 报错点名工作区根因**(上报③:`db init` 前报错不提示 YGL_HOME):无库时错误带 `workspace: <path>` 与来源(YGL_HOME 显式 / 默认 ~/.ygl-trees),冷启动错环境一眼可辨
|
|
8
|
+
- **`node can --eval` attachments 快照**(建议④:P 值过了看不出吃了哪些 rel 证据):输出回显本次判定实际吃到的 rel 挂接——`--json` 增 `eval_attachments[]`,人类视图 `attachments ×N:` 逐条(rel ← type:id 摘要),复现不必翻库;无挂接时字段省略(字节兼容)
|
|
9
|
+
- **job 子命令无锁启动**(建议⑤配套):`job run/tick` 的子进程带 `YGL_SERVE=1` 启动——此前 runner 自持应用级写锁,任何 `ygl-trees …` 型任务命令必撞自身死锁(rc=1 "locked");WAL 并发读 + SQLite 层写串行,与 webui serve 同推理。到期巡检范式:`job add "到期巡检" --every 1440 -- ygl-trees node cond --due --days 30`(真实工作区已挂,30 天窗口即刻命中 w1 三门槛)
|
|
10
|
+
- 测试:demo 5k 段 7 断言(--project 钉住 / 落点校验 / 半写拒绝+零落地 / --help rc=0 / attachments 快照)+ 5h `--tree` 别名断言 → 113;readme-verify +3(kb add --help、source add --project、node list --tree 点名)→ 46
|
|
11
|
+
|
|
12
|
+
## 0.82.1(2026-10-17)预测生命周期护栏:挂证据 ≠ 仲裁
|
|
13
|
+
- **`claim link` 不再抢跑预测仲裁**(用户上报③:`--stance supports` 自动把 [预测·…] 断言置 supported,predict audit 判 supported||refuted → resolved——预测刚挂证据就退出跟踪表,实际无人仲裁):预测前缀断言挂证据时跳过自动晋升,状态保持 unverified 直到显式 `claim resolve` / 到期回测;证据链接与 stance 照常落库,回执明示 `auto_promote: false`(`--json` 返回 `{claim, evidence, stance, status, auto_promote}`);普通断言的自动推导不变(回归护栏)
|
|
14
|
+
- **`claim unresolve <claimId>`**(建议②:误晋升后无归位命令):复位通道把断言重置回 unverified(撤销链接副作用 / 抢跑仲裁),幂等(已 unverified → `duplicate:true`),落 `claim.unresolve` 事件留痕——0.82.1 前被误晋升的预测可用它归队
|
|
15
|
+
- **predict audit `--json` 覆盖确认**(建议③):`--json` 始终输出全量 rows(含 resolved 终态行),人类视图默认隐藏终态——机器读全量、人看增量,同构无缺口
|
|
16
|
+
- 测试:predlink.spec.ts 5 例(预测不晋升 / 普通照常推导 / unresolve 复位+事件 / 幂等 / 坏 id 点名)→ 177;demo 5j 段 5 断言(挂证据不晋升 / 普通推导回归 / unresolve 复位 / 幂等 / 复位后仍在跟踪表)→ 106
|
|
17
|
+
|
|
3
18
|
## 0.82.0(2026-10-17)跨树巡检 + 临期 eval 门槛 + 幻影对账一键修复
|
|
4
19
|
- **`node ready --all`**(用户建议②:12 棵树巡检要 24 条命令——每树 tree use + ready):一次进程扫全部树的就绪集,无需 tree use / `-t`;`--json` 返回 `[{tree, ready[]}]`,人类输出按树分节
|
|
5
20
|
- **`node cond --due [--days N] [--eval]`**(建议③:w1/d90 检验依赖人记得看):扫描全部 eval 族条件,提取问题文本中的 `YYYY-MM-DD` 期限,列出 N 天内到期(含逾期)的条件(树/节点/deadline/剩余天数);`--days` 放宽窗口(默认 7);`--eval` 逐条组装上下文包送判定模型(三题型按 op 自动选择,离线安全回落 pending)——**判定只报告不仲裁**,通过 ≠ 自动解锁,resolve/unlock 保持人工(与 predict audit 同纪律);跨树扫描前置于树解析,无当前树也能跑
|
package/README.md
CHANGED
|
@@ -109,9 +109,12 @@ ygl-trees node import --file tree.json --tree copy # 严格校验、事务导
|
|
|
109
109
|
|
|
110
110
|
**跨树巡检 + 临期门槛(0.82)**:`node ready --all` 一次扫全部树(12 棵树 24 条命令 → 1 条);`node cond --due [--days N]` 列出问题文本带 `YYYY-MM-DD` 期限且 N 天内到期(含逾期)的 eval 条件——w1/d90 预测门槛不再依赖人记得看;`--eval` 逐条送判定模型(离线安全回落 pending),**判定只报告不仲裁**(通过 ≠ 自动解锁,resolve/unlock 保持人工,与 predict audit 同纪律)。
|
|
111
111
|
|
|
112
|
+
**并发钉住 + 半写防护(0.83,真实用户报告)**:并发会话共享 `current_project` 指针互踩导致资产碎片化——研究/产出/树三域写命令全部接受长旗标钉住:`research * --project <p>`、`produce/task * --project <p>`、`node * --tree <t>`(短旗标 `-p`/`-t` 不变);写回执带 `· project <名>` 回显落点,写错项目当场可见。`source add --query` 引用不存在时**先拒绝后写入**(rc=1、零落地),不再出现"源已落、链接失败"的半写;`job run/tick` 的子命令以无锁环境启动(WAL 并发读),`ygl-trees …` 型任务命令不再与运行器自身死锁。`--help` 在任意深度(如 `kb add --help`)打组帮助 rc=0;`db init` 前的报错提示 `YGL_HOME` 工作区根因;到期巡检可直接挂 job(`job add "到期巡检" --every 1440 -- ygl-trees node cond --due --days 30`)。
|
|
113
|
+
|
|
112
114
|
**决策模型评估门(spec R8;0.14 布尔 → 0.26 三题型 → 0.27 混批 → 0.80 织物并入)**:语义型必检条件用 eval 题型声明,`node can <key> --eval` 会把**节点元数据 + 直接父(关联)+ 祖先链(闭包索引)+ 条件组 + 已解锁集 + --state 袋 + rel 挂接摘要**组装为上下文包,交给评估模型(TypeSafe jev,经 Vercel AI Gateway)判定:
|
|
113
115
|
|
|
114
116
|
- **rel 织物并入(0.80)**:节点挂接的 evidence(quote 摘要)/ claim(text+status+confidence)/ kb/source(标题)等 rel 关系(双向,≤12 条,文本截 120 字)自动进上下文包的 `attachments`——「证据挂链」纪律从此对语义门槛可见,不必再 `--state k=v` 手动携带裁决事实(实测 P 0.30 → 0.92 的差距由挂链补平)
|
|
117
|
+
- **attachments 快照(0.83)**:`node can <key> --eval` 的输出(`--json` 的 `eval_attachments[]` + 人类视图 `attachments ×N:`)回显本次判定实际吃到的 rel 挂接——P 值过了也能一眼看出依据是哪几条 evidence/claim,复现不必翻库
|
|
115
118
|
|
|
116
119
|
- **三题型**:`--op eval`(布尔 P(true))/ `--op eval-choice`(多选一,判 **P(期望答案)**,`--options "k=desc,..."` + `--answer k`)/ `--op eval-score`(等级评分 `--levels "级0,..."` 2-10 级 + `--min-score n`,判概率加权均值 ±1 灰带)
|
|
117
120
|
- P≥0.7 通过 · P≤0.4 未通过 · **灰带 pending**(过程态,禁作终态,单列不阻塞也不放行);score 的灰带是 [min−1, min)
|
|
@@ -131,14 +134,17 @@ ygl-trees node import --file tree.json --tree copy # 严格校验、事务导
|
|
|
131
134
|
```
|
|
132
135
|
query add 登记检索式
|
|
133
136
|
query attach 检索式 ↔ 来源关联(query list 显示召回数)
|
|
134
|
-
source add 收来源,按 (project, url_hash) 去重;--query
|
|
137
|
+
source add 收来源,按 (project, url_hash) 去重;--query 顺带关联(引用不存在 → 整体拒绝 rc=1,零落地)
|
|
138
|
+
全组接受 --project <p> 长旗标钉住目标项目(并发会话不依赖共享 current 指针;回执带 · project 回显)
|
|
135
139
|
--adjudication "<裁决摘要>":会话内裁决原生载体(url 自动 session://adjudication/<时刻>,摘要兼作标题);--url 收任意 <scheme>://(https/file/session/a2a/… 跨 Agent 引用免落盘)
|
|
136
140
|
source fetch <url> 直接抓取网页入库:title/meta description/去标签正文;15s 超时、非 html 拒收、失败零副作用,正文过薄自动打 needs_review
|
|
137
141
|
evidence add 从来源摘原文片段,带 locator(页码/章节);长摘录用 --file <path> 免转义
|
|
138
142
|
claim add 写下断言;--confidence 收 A|B|C 或数值档(0.92 / 85% 原样入库)
|
|
139
143
|
claim link 断言 ↔ 证据,自动算 supported / refuted / contested
|
|
140
144
|
--by-source:传来源 id 一次挂齐其全部证据;误传来源 id 会列出该来源的证据 id
|
|
145
|
+
[预测·…] 断言不自动晋升(0.82.1):挂证据≠仲裁,状态保持 unverified 直到显式 resolve / 到期回测
|
|
141
146
|
claim resolve 人工仲裁出口:--as supported|refuted|contested,事件留痕;后续新证据链接会重新推导
|
|
147
|
+
claim unresolve 复位通道:把断言重置回 unverified(撤销链接副作用 / 抢跑仲裁),幂等 duplicate:true
|
|
142
148
|
predict audit 预测回测:扫描 [预测·N天] 断言,本地可测判据自动测(树/kb 计数、npm 版本数),判 hit/miss/pending/manual——只报告不仲裁,给出待 resolve 建议
|
|
143
149
|
--state <file|url>:外部计数桥接(如 DSH API 快照),判据里的裸键名后缀匹配嵌套键(y_imported≥12 ← counts.y_imported)
|
|
144
150
|
已 claim resolve 的预测默认隐藏(回测终态),--all 查看全表
|
|
@@ -294,7 +300,7 @@ ygl-trees agent ask "<question>" natural language interface
|
|
|
294
300
|
## 五b、开发与运维工具
|
|
295
301
|
|
|
296
302
|
```bash
|
|
297
|
-
pnpm test # 四段制回归:unit (vitest
|
|
303
|
+
pnpm test # 四段制回归:unit (vitest 177 例) + build (tsc + tsdown) + demo(113 条断言)+ contract:audit(86 项)
|
|
298
304
|
pnpm bench # 性能基准七席(startup/写路径/检索/索引),每席含 RSS 内存端点 → tmp/bench-results.json
|
|
299
305
|
node benchmarks/bench.js --quick # 缩小规模快速跑
|
|
300
306
|
pnpm pack:check # 发布就绪门禁:打包 → 隔离安装 → 驱动发布产物跑命令面 + --json 纯净性
|
package/dist/apps/cli/src/bin.js
CHANGED
|
@@ -42,6 +42,7 @@ exports.loadBundle = loadBundle;
|
|
|
42
42
|
exports.repoRoot = repoRoot;
|
|
43
43
|
exports.appVersion = appVersion;
|
|
44
44
|
exports.usage = usage;
|
|
45
|
+
exports.groupHelp = groupHelp;
|
|
45
46
|
exports.main = main;
|
|
46
47
|
exports.generateLlmsManifest = generateLlmsManifest;
|
|
47
48
|
/**
|
|
@@ -163,6 +164,7 @@ ${(0, fmt_1.paint)(fmt_1.C.bold, "research", true)} collect → screen → ex
|
|
|
163
164
|
claim link <claimId> <evidenceId> [--stance supports|refutes]
|
|
164
165
|
--by-source <claimId> <sourceId>: link ALL evidence under a source
|
|
165
166
|
claim resolve <claimId> --as supported|refuted|contested [--note]
|
|
167
|
+
claim unresolve <claimId> reset to unverified (undo auto-promote / early arbitration)
|
|
166
168
|
claim list
|
|
167
169
|
|
|
168
170
|
${(0, fmt_1.paint)(fmt_1.C.bold, "produce", true)} outline → section → report · spec/design(工程产出)
|
|
@@ -250,6 +252,25 @@ Global: --json --no-color --db <file> --home <dir>
|
|
|
250
252
|
function usage() {
|
|
251
253
|
return USAGE;
|
|
252
254
|
}
|
|
255
|
+
/** 0.83 (user report ②): `--help` at any depth prints the relevant usage
|
|
256
|
+
* slice — `kb add --help` used to fall into flag validation rc=2. Renders the
|
|
257
|
+
* group's own block (found by its bold header line); unknown groups get the
|
|
258
|
+
* full usage. */
|
|
259
|
+
function groupHelp(group) {
|
|
260
|
+
const pfx = (0, fmt_1.paint)(fmt_1.C.bold, group, true);
|
|
261
|
+
const lines = USAGE.split("\n");
|
|
262
|
+
const start = lines.findIndex((l) => l.startsWith(pfx));
|
|
263
|
+
if (start < 0)
|
|
264
|
+
return USAGE;
|
|
265
|
+
let end = lines.length;
|
|
266
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
267
|
+
if (lines[i].startsWith("\u001b[1m")) {
|
|
268
|
+
end = i;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return lines.slice(0, 4).join("\n") + "\n\n" + lines.slice(start, end).join("\n").trimEnd();
|
|
273
|
+
}
|
|
253
274
|
// ── Main ──
|
|
254
275
|
async function main(argv) {
|
|
255
276
|
const args = (0, types_1.parse)(argv);
|
|
@@ -290,6 +311,17 @@ async function main(argv) {
|
|
|
290
311
|
(0, fmt_1.out)(ctx, appVersion());
|
|
291
312
|
return 0;
|
|
292
313
|
}
|
|
314
|
+
// 0.83 (user report ②): `--help` at ANY depth prints the group's usage
|
|
315
|
+
// slice rc=0 — `kb add --help` used to fall into flag validation rc=2.
|
|
316
|
+
// Fires before dispatch and before db boot: help never touches the db.
|
|
317
|
+
if (args.flags.help === true && group) {
|
|
318
|
+
const text = groupHelp(group);
|
|
319
|
+
if (ctx.json)
|
|
320
|
+
console.log(JSON.stringify({ help: text.replace(/\u001b\[[0-9;]*m/g, "") }, null, 2));
|
|
321
|
+
else
|
|
322
|
+
(0, fmt_1.out)(ctx, text);
|
|
323
|
+
return 0;
|
|
324
|
+
}
|
|
293
325
|
// llms: structured capability manifest for LLM consumption (Ant Design
|
|
294
326
|
// "for-agents" pattern — one output gives any Agent the full command surface).
|
|
295
327
|
// Onboarding: detect first run (no DB) and guide to db init
|
|
@@ -363,6 +395,10 @@ async function main(argv) {
|
|
|
363
395
|
if (needInit && !require("node:fs").existsSync(file)) {
|
|
364
396
|
(0, fmt_1.bad)(ctx, `no database at ${file}`);
|
|
365
397
|
(0, fmt_1.out)(ctx, ` run: ygl-trees db init`);
|
|
398
|
+
// 0.83 (user report ③): name the workspace root cause — a foreign
|
|
399
|
+
// YGL_HOME (or its absence) is invisible otherwise, and the user chases
|
|
400
|
+
// the db instead of the environment.
|
|
401
|
+
(0, fmt_1.out)(ctx, ` workspace: ${home}${process.env.YGL_HOME ? " (from YGL_HOME)" : " (default ~/.ygl-trees — set YGL_HOME to switch)"}`);
|
|
366
402
|
return 2;
|
|
367
403
|
}
|
|
368
404
|
// ── Cordis composition via bootApp (DSH profile-boot exemplar) ──
|
|
@@ -664,6 +700,7 @@ function generateLlmsManifest() {
|
|
|
664
700
|
["research claim add \"<text>\"", "refutable assertion"],
|
|
665
701
|
["research claim link <cid> <eid> --stance supports|refutes", "link claim to evidence"],
|
|
666
702
|
["research claim resolve <cid> --as supported|refuted|contested", "manual arbitration"],
|
|
703
|
+
["research claim unresolve <cid>", "reset to unverified (undo link auto-promote)"],
|
|
667
704
|
["research claim evaluate <cid>", "decision model confidence (0-5 → A/B/C/D)"],
|
|
668
705
|
["research evidence assess <eid>", "decision model quality assessment"],
|
|
669
706
|
]],
|
package/dist/bundle/bin.cjs
CHANGED
|
@@ -4088,6 +4088,7 @@ var writes_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
|
4088
4088
|
claimAdd: () => claimAdd,
|
|
4089
4089
|
claimLink: () => claimLink,
|
|
4090
4090
|
claimResolve: () => claimResolve,
|
|
4091
|
+
claimUnresolve: () => claimUnresolve,
|
|
4091
4092
|
evidenceAdd: () => evidenceAdd,
|
|
4092
4093
|
jobAdd: () => jobAdd,
|
|
4093
4094
|
kbAdd: () => kbAdd,
|
|
@@ -4343,7 +4344,8 @@ function mapClaim(c) {
|
|
|
4343
4344
|
}
|
|
4344
4345
|
async function claimLink(ds, claimId, evidenceId, stance = "supports") {
|
|
4345
4346
|
return ds.transaction(async (mgr) => {
|
|
4346
|
-
|
|
4347
|
+
const c = await mgr.query(`SELECT id, text FROM claims WHERE id=?`, [claimId]);
|
|
4348
|
+
if (!c.length) throw new Error(`claim not found: ${claimId}`);
|
|
4347
4349
|
if (!(await mgr.query(`SELECT id FROM evidence WHERE id=?`, [evidenceId])).length) throw new Error(`evidence not found: ${evidenceId}`);
|
|
4348
4350
|
await mgr.query(`INSERT INTO claim_evidence(claim_id,evidence_id,stance) VALUES(?,?,?)
|
|
4349
4351
|
ON CONFLICT(claim_id,evidence_id) DO UPDATE SET stance=excluded.stance`, [
|
|
@@ -4351,6 +4353,15 @@ async function claimLink(ds, claimId, evidenceId, stance = "supports") {
|
|
|
4351
4353
|
evidenceId,
|
|
4352
4354
|
stance
|
|
4353
4355
|
]);
|
|
4356
|
+
if (String(c[0]?.text ?? "").startsWith("[预测·")) {
|
|
4357
|
+
const cur = await mgr.query(`SELECT status FROM claims WHERE id=?`, [claimId]);
|
|
4358
|
+
const status = String(cur?.[0]?.status ?? "unverified");
|
|
4359
|
+
await logEvent$1(mgr, "claim.link", "claim", claimId, `${stance} ${evidenceId} (prediction: auto-promote skipped)`);
|
|
4360
|
+
return {
|
|
4361
|
+
status,
|
|
4362
|
+
auto_promote: false
|
|
4363
|
+
};
|
|
4364
|
+
}
|
|
4354
4365
|
const sup = await mgr.query(`SELECT COUNT(*) AS c FROM claim_evidence WHERE claim_id=? AND stance='supports'`, [claimId]);
|
|
4355
4366
|
const ref = await mgr.query(`SELECT COUNT(*) AS c FROM claim_evidence WHERE claim_id=? AND stance='refutes'`, [claimId]);
|
|
4356
4367
|
const s = Number(sup?.[0]?.c ?? 0);
|
|
@@ -4358,7 +4369,33 @@ async function claimLink(ds, claimId, evidenceId, stance = "supports") {
|
|
|
4358
4369
|
const status = s && r ? "contested" : s ? "supported" : r ? "refuted" : "unverified";
|
|
4359
4370
|
await mgr.update(Claim, { id: claimId }, { status });
|
|
4360
4371
|
await logEvent$1(mgr, "claim.link", "claim", claimId, `${stance} ${evidenceId}`);
|
|
4361
|
-
return {
|
|
4372
|
+
return {
|
|
4373
|
+
status,
|
|
4374
|
+
auto_promote: true
|
|
4375
|
+
};
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4378
|
+
/** 0.82.1 (user report ③): reset an auto-promoted / wrongly arbitrated claim
|
|
4379
|
+
* back to unverified — the repair channel for link side effects on
|
|
4380
|
+
* predictions (and for any claim arbitrated too early). Idempotent: already
|
|
4381
|
+
* unverified → duplicate:true, no event spam. */
|
|
4382
|
+
async function claimUnresolve(ds, claimId) {
|
|
4383
|
+
return ds.transaction(async (mgr) => {
|
|
4384
|
+
const c = await mgr.query(`SELECT id, status FROM claims WHERE id=?`, [claimId]);
|
|
4385
|
+
if (!c.length) throw new Error(`claim not found: ${claimId}`);
|
|
4386
|
+
if (String(c[0]?.status) === "unverified") return {
|
|
4387
|
+
status: "unverified",
|
|
4388
|
+
duplicate: true
|
|
4389
|
+
};
|
|
4390
|
+
await mgr.update(Claim, { id: claimId }, {
|
|
4391
|
+
status: "unverified",
|
|
4392
|
+
updated: require_text.nowIso()
|
|
4393
|
+
});
|
|
4394
|
+
await logEvent$1(mgr, "claim.unresolve", "claim", claimId, `${c[0]?.status} → unverified`);
|
|
4395
|
+
return {
|
|
4396
|
+
status: "unverified",
|
|
4397
|
+
duplicate: false
|
|
4398
|
+
};
|
|
4362
4399
|
});
|
|
4363
4400
|
}
|
|
4364
4401
|
async function claimResolve(ds, claimId, status, note = "") {
|
|
@@ -5751,7 +5788,11 @@ function execute(env, job) {
|
|
|
5751
5788
|
"pipe",
|
|
5752
5789
|
"pipe"
|
|
5753
5790
|
],
|
|
5754
|
-
timeout: 6e5
|
|
5791
|
+
timeout: 6e5,
|
|
5792
|
+
env: {
|
|
5793
|
+
...process.env,
|
|
5794
|
+
YGL_SERVE: "1"
|
|
5795
|
+
}
|
|
5755
5796
|
});
|
|
5756
5797
|
rc = 0;
|
|
5757
5798
|
} catch (e) {
|
|
@@ -6594,7 +6635,7 @@ function run$8(env, action, rest, args) {
|
|
|
6594
6635
|
const subAction = rest[0];
|
|
6595
6636
|
let project;
|
|
6596
6637
|
try {
|
|
6597
|
-
project = store.projectResolve(args.flags.p ? String(args.flags.p) : void 0);
|
|
6638
|
+
project = store.projectResolve(args.flags.p ?? args.flags.project ? String(args.flags.p ?? args.flags.project) : void 0);
|
|
6598
6639
|
} catch (e) {
|
|
6599
6640
|
bad(ctx, e.message);
|
|
6600
6641
|
return 1;
|
|
@@ -6733,12 +6774,25 @@ function run$8(env, action, rest, args) {
|
|
|
6733
6774
|
tags: String(args.flags.tags || "")
|
|
6734
6775
|
};
|
|
6735
6776
|
const qRef = String(args.flags.query || "");
|
|
6777
|
+
if (qRef) {
|
|
6778
|
+
if (!store.db.get("SELECT id FROM queries WHERE (id=? OR text=?) AND project_id=?", [
|
|
6779
|
+
qRef,
|
|
6780
|
+
qRef,
|
|
6781
|
+
project.id
|
|
6782
|
+
])) {
|
|
6783
|
+
bad(ctx, `query not found: ${require_text.clip(qRef, 40)} — refusing to write (source add would land without its query link; fix the ref or drop --query)`);
|
|
6784
|
+
return 1;
|
|
6785
|
+
}
|
|
6786
|
+
}
|
|
6736
6787
|
return getOrm(env.db.path).then(async (ds) => {
|
|
6737
6788
|
const r = await sourceAdd(ds, project.id, spec);
|
|
6738
|
-
writeResult(ctx, r, (row) => `source added: ${row.id} ${require_text.clip(row.title, 60)}`);
|
|
6739
|
-
if (qRef) {
|
|
6789
|
+
writeResult(ctx, r, (row) => `source added: ${row.id} ${require_text.clip(row.title, 60)} · project ${project.name}`);
|
|
6790
|
+
if (qRef) try {
|
|
6740
6791
|
const at = await queryAttach(ds, project.id, qRef, r.row.id);
|
|
6741
6792
|
if (!ctx.json && !at.dup) out(ctx, paint(C.dim, ` attached to query: "${require_text.clip(at.queryText, 40)}"`, ctx.color));
|
|
6793
|
+
} catch (e) {
|
|
6794
|
+
bad(ctx, `source landed as ${r.row.id} but attach failed: ${e?.message} — research query attach ${require_text.clip(qRef, 20)} ${r.row.id} to retry`);
|
|
6795
|
+
return 1;
|
|
6742
6796
|
}
|
|
6743
6797
|
return 0;
|
|
6744
6798
|
}).catch((e) => {
|
|
@@ -6911,7 +6965,7 @@ function run$8(env, action, rest, args) {
|
|
|
6911
6965
|
}
|
|
6912
6966
|
const note = String(args.flags.note || "");
|
|
6913
6967
|
const locator = String(args.flags.locator || "");
|
|
6914
|
-
return runWrite(env, (ds) => evidenceAdd(ds, project.id, sourceId, quote, note, locator, force), (r) => `evidence added: ${r.id} ${require_text.clip(quote, 50)}`);
|
|
6968
|
+
return runWrite(env, (ds) => evidenceAdd(ds, project.id, sourceId, quote, note, locator, force), (r) => `evidence added: ${r.id} ${require_text.clip(quote, 50)} · project ${project.name}`);
|
|
6915
6969
|
}
|
|
6916
6970
|
if (action === "evidence" && subAction === "assess") {
|
|
6917
6971
|
const id = rest[1];
|
|
@@ -7007,7 +7061,7 @@ function run$8(env, action, rest, args) {
|
|
|
7007
7061
|
return 2;
|
|
7008
7062
|
}
|
|
7009
7063
|
const confidence = String(args.flags.confidence || configGet(env.db, "default_confidence"));
|
|
7010
|
-
return runWrite(env, (ds) => claimAdd(ds, project.id, text, confidence, force), (r) => `claim added: ${r.id} ${require_text.clip(text, 60)}`);
|
|
7064
|
+
return runWrite(env, (ds) => claimAdd(ds, project.id, text, confidence, force), (r) => `claim added: ${r.id} ${require_text.clip(text, 60)} · project ${project.name}`);
|
|
7011
7065
|
}
|
|
7012
7066
|
if (subAction === "link") {
|
|
7013
7067
|
if (!rest[1] || !rest[2]) {
|
|
@@ -7049,7 +7103,15 @@ function run$8(env, action, rest, args) {
|
|
|
7049
7103
|
});
|
|
7050
7104
|
}
|
|
7051
7105
|
return getOrm(env.db.path).then((ds) => claimLink(ds, rest[1], rest[2], stance)).then((r) => {
|
|
7052
|
-
|
|
7106
|
+
if (ctx.json) console.log(JSON.stringify({
|
|
7107
|
+
claim: rest[1],
|
|
7108
|
+
evidence: rest[2],
|
|
7109
|
+
stance,
|
|
7110
|
+
status: r.status,
|
|
7111
|
+
auto_promote: r.auto_promote
|
|
7112
|
+
}, null, 2));
|
|
7113
|
+
else if (r.auto_promote === false) ok(ctx, `linked; prediction claim stays ${r.status} — auto-promote skipped (arbitration: claim resolve / deadline audit)`);
|
|
7114
|
+
else ok(ctx, `linked; claim status now: ${r.status}`);
|
|
7053
7115
|
try {
|
|
7054
7116
|
const { evaluateClaimConfidence } = (init_eval_enhance(), require_rolldown_runtime.__toCommonJS(eval_enhance_exports));
|
|
7055
7117
|
const evRows = store.db.all(`SELECT e.quote, ce.stance, s.title AS source_title
|
|
@@ -7129,6 +7191,30 @@ function run$8(env, action, rest, args) {
|
|
|
7129
7191
|
return 1;
|
|
7130
7192
|
});
|
|
7131
7193
|
}
|
|
7194
|
+
if (subAction === "unresolve") {
|
|
7195
|
+
const id = rest[1];
|
|
7196
|
+
if (!id) {
|
|
7197
|
+
bad(ctx, "usage: research claim unresolve <claimId>");
|
|
7198
|
+
return 2;
|
|
7199
|
+
}
|
|
7200
|
+
const c0 = store.db.get("SELECT id, status FROM claims WHERE id=? AND project_id=?", [id, project.id]);
|
|
7201
|
+
if (!c0) {
|
|
7202
|
+
bad(ctx, `claim not found: ${id}`);
|
|
7203
|
+
return 1;
|
|
7204
|
+
}
|
|
7205
|
+
return getOrm(env.db.path).then((ds) => claimUnresolve(ds, id)).then((r) => {
|
|
7206
|
+
if (ctx.json) console.log(JSON.stringify({
|
|
7207
|
+
id,
|
|
7208
|
+
status: r.status,
|
|
7209
|
+
duplicate: r.duplicate
|
|
7210
|
+
}, null, 2));
|
|
7211
|
+
else ok(ctx, r.duplicate ? `claim ${id} already unverified (no change)` : `claim ${id}: ${c0.status} → unverified (back in tracking; arbitration: claim resolve)`);
|
|
7212
|
+
return 0;
|
|
7213
|
+
}).catch((e) => {
|
|
7214
|
+
bad(ctx, e?.message || String(e));
|
|
7215
|
+
return 1;
|
|
7216
|
+
});
|
|
7217
|
+
}
|
|
7132
7218
|
if (subAction === "evaluate") {
|
|
7133
7219
|
const id = rest[1];
|
|
7134
7220
|
if (!id) {
|
|
@@ -7356,7 +7442,7 @@ function run$7(env, action, rest, args) {
|
|
|
7356
7442
|
ack(ctx, { project: name }, `now using: ${name}`);
|
|
7357
7443
|
return 0;
|
|
7358
7444
|
case "show": {
|
|
7359
|
-
const resolved = store.projectResolve(args.flags.p ? String(args.flags.p) : void 0);
|
|
7445
|
+
const resolved = store.projectResolve(args.flags.p ?? args.flags.project ? String(args.flags.p ?? args.flags.project) : void 0);
|
|
7360
7446
|
return getOrm(env.db.path).then(async (ds) => {
|
|
7361
7447
|
const proj = await ds.getRepository(Project).findOne({
|
|
7362
7448
|
where: { id: resolved.id },
|
|
@@ -7777,7 +7863,7 @@ function run$5(env, action, rest, args) {
|
|
|
7777
7863
|
}
|
|
7778
7864
|
let project;
|
|
7779
7865
|
try {
|
|
7780
|
-
project = store.projectResolve(args.flags.p ? String(args.flags.p) : void 0);
|
|
7866
|
+
project = store.projectResolve(args.flags.p ?? args.flags.project ? String(args.flags.p ?? args.flags.project) : void 0);
|
|
7781
7867
|
} catch (e) {
|
|
7782
7868
|
bad(ctx, e.message);
|
|
7783
7869
|
return 1;
|
|
@@ -7889,7 +7975,7 @@ function run$5(env, action, rest, args) {
|
|
|
7889
7975
|
return getOrm(env.db.path).then((ds) => taskAdd(ds, pid, title, note)).then((row) => {
|
|
7890
7976
|
if (!nodeRef) {
|
|
7891
7977
|
if (ctx.json) console.log(JSON.stringify(row, null, 2));
|
|
7892
|
-
else ok(ctx, `task added: ${row.id} ${title}`);
|
|
7978
|
+
else ok(ctx, `task added: ${row.id} ${title} · project ${project.name}`);
|
|
7893
7979
|
return 0;
|
|
7894
7980
|
}
|
|
7895
7981
|
return relAdd(env, `node:${nodeRef}`, `task:${row.id}`, "tracked-by").then((r) => {
|
|
@@ -8713,7 +8799,7 @@ require_text.init_text();
|
|
|
8713
8799
|
function run$4(env, action, rest, args) {
|
|
8714
8800
|
const { ctx, db } = env;
|
|
8715
8801
|
const trees = new TreeStore(db);
|
|
8716
|
-
const sel = args.flags.t ? String(args.flags.t) : void 0;
|
|
8802
|
+
const sel = !(action === "import") ? args.flags.t ?? args.flags.tree ? String(args.flags.t ?? args.flags.tree) : void 0 : args.flags.t ? String(args.flags.t) : void 0;
|
|
8717
8803
|
const flagNum = (k, d) => {
|
|
8718
8804
|
const v = Number(args.flags[k]);
|
|
8719
8805
|
return Number.isFinite(v) ? v : d;
|
|
@@ -9274,6 +9360,8 @@ function run$4(env, action, rest, args) {
|
|
|
9274
9360
|
}
|
|
9275
9361
|
r.pending = pendingList;
|
|
9276
9362
|
r.evaluated = evaluated;
|
|
9363
|
+
const atts = contextPkg.attachments;
|
|
9364
|
+
if (Array.isArray(atts) && atts.length) r.eval_attachments = atts;
|
|
9277
9365
|
r.ok = r.missing_required.length === 0 && r.blocked_by.length === 0 && pendingList.length === 0;
|
|
9278
9366
|
try {
|
|
9279
9367
|
const run = process.env.YGL_RUN_ID || "";
|
|
@@ -9357,6 +9445,13 @@ function run$4(env, action, rest, args) {
|
|
|
9357
9445
|
for (const m of r.missing_optional) out(ctx, ` - ${m}`);
|
|
9358
9446
|
}
|
|
9359
9447
|
for (const e of r.evaluated) out(ctx, paint(C.dim, ` eval ${e.name}: P=${e.p ?? "-"} → ${e.outcome}${e.reason ? ` (${e.reason})` : ""}`, ctx.color));
|
|
9448
|
+
if (Array.isArray(r.eval_attachments) && r.eval_attachments.length) {
|
|
9449
|
+
out(ctx, paint(C.dim, ` attachments ×${r.eval_attachments.length}:`, ctx.color));
|
|
9450
|
+
for (const a of r.eval_attachments) {
|
|
9451
|
+
const label = a.type === "claim" ? `${a.id}(${a.status})` : a.type === "evidence" ? `${a.id} "${require_text.clip(a.quote, 36)}"` : a.type === "kb" || a.type === "source" ? `${a.id} ${require_text.clip(a.title, 30)}` : a.type === "node" ? `${a.key ?? a.id}` : String(a.id);
|
|
9452
|
+
out(ctx, paint(C.dim, ` - ${a.rel} ← ${a.type}:${label}`, ctx.color));
|
|
9453
|
+
}
|
|
9454
|
+
}
|
|
9360
9455
|
if (r.cost_multiplier !== 1) out(ctx, ` 成本系数 ${r.cost_multiplier}`);
|
|
9361
9456
|
return 0;
|
|
9362
9457
|
}
|
|
@@ -9648,10 +9743,11 @@ function run$4(env, action, rest, args) {
|
|
|
9648
9743
|
if (sub === "list" || sub === void 0) {
|
|
9649
9744
|
for (const f of Object.keys(args.flags || {})) if (![
|
|
9650
9745
|
"t",
|
|
9746
|
+
"tree",
|
|
9651
9747
|
"json",
|
|
9652
9748
|
"no-color"
|
|
9653
9749
|
].includes(f)) {
|
|
9654
|
-
bad(ctx, `unknown flag --${f} for 'node list' — tree selection is '-t <tree>' (positional <tree> also works)`);
|
|
9750
|
+
bad(ctx, `unknown flag --${f} for 'node list' — tree selection is '-t|--tree <tree>' (positional <tree> also works)`);
|
|
9655
9751
|
return 2;
|
|
9656
9752
|
}
|
|
9657
9753
|
table(ctx, trees.nodeList(tid), [
|
|
@@ -11979,6 +12075,7 @@ ${paint(C.bold, "research", true)} collect → screen → extract → claim
|
|
|
11979
12075
|
claim link <claimId> <evidenceId> [--stance supports|refutes]
|
|
11980
12076
|
--by-source <claimId> <sourceId>: link ALL evidence under a source
|
|
11981
12077
|
claim resolve <claimId> --as supported|refuted|contested [--note]
|
|
12078
|
+
claim unresolve <claimId> reset to unverified (undo auto-promote / early arbitration)
|
|
11982
12079
|
claim list
|
|
11983
12080
|
|
|
11984
12081
|
${paint(C.bold, "produce", true)} outline → section → report · spec/design(工程产出)
|
|
@@ -12066,6 +12163,22 @@ Global: --json --no-color --db <file> --home <dir>
|
|
|
12066
12163
|
function usage() {
|
|
12067
12164
|
return USAGE;
|
|
12068
12165
|
}
|
|
12166
|
+
/** 0.83 (user report ②): `--help` at any depth prints the relevant usage
|
|
12167
|
+
* slice — `kb add --help` used to fall into flag validation rc=2. Renders the
|
|
12168
|
+
* group's own block (found by its bold header line); unknown groups get the
|
|
12169
|
+
* full usage. */
|
|
12170
|
+
function groupHelp(group) {
|
|
12171
|
+
const pfx = paint(C.bold, group, true);
|
|
12172
|
+
const lines = USAGE.split("\n");
|
|
12173
|
+
const start = lines.findIndex((l) => l.startsWith(pfx));
|
|
12174
|
+
if (start < 0) return USAGE;
|
|
12175
|
+
let end = lines.length;
|
|
12176
|
+
for (let i = start + 1; i < lines.length; i++) if (lines[i].startsWith("\x1B[1m")) {
|
|
12177
|
+
end = i;
|
|
12178
|
+
break;
|
|
12179
|
+
}
|
|
12180
|
+
return lines.slice(0, 4).join("\n") + "\n\n" + lines.slice(start, end).join("\n").trimEnd();
|
|
12181
|
+
}
|
|
12069
12182
|
async function main(argv) {
|
|
12070
12183
|
const args = parse(argv);
|
|
12071
12184
|
const ctx = {
|
|
@@ -12081,6 +12194,12 @@ async function main(argv) {
|
|
|
12081
12194
|
out(ctx, appVersion());
|
|
12082
12195
|
return 0;
|
|
12083
12196
|
}
|
|
12197
|
+
if (args.flags.help === true && group) {
|
|
12198
|
+
const text = groupHelp(group);
|
|
12199
|
+
if (ctx.json) console.log(JSON.stringify({ help: text.replace(/\u001b\[[0-9;]*m/g, "") }, null, 2));
|
|
12200
|
+
else out(ctx, text);
|
|
12201
|
+
return 0;
|
|
12202
|
+
}
|
|
12084
12203
|
if (!group || group === "help") {
|
|
12085
12204
|
const dbFile = require("node:path").join(process.env.YGL_HOME || require("node:path").join(require("node:os").homedir(), ".ygl-trees"), "ygl-trees.db");
|
|
12086
12205
|
if (!require("node:fs").existsSync(dbFile)) {
|
|
@@ -12141,6 +12260,7 @@ async function main(argv) {
|
|
|
12141
12260
|
if (!(group === "db" && action === "init") && group !== "plugin" && !require("node:fs").existsSync(file)) {
|
|
12142
12261
|
bad(ctx, `no database at ${file}`);
|
|
12143
12262
|
out(ctx, ` run: ygl-trees db init`);
|
|
12263
|
+
out(ctx, ` workspace: ${home}${process.env.YGL_HOME ? " (from YGL_HOME)" : " (default ~/.ygl-trees — set YGL_HOME to switch)"}`);
|
|
12144
12264
|
return 2;
|
|
12145
12265
|
}
|
|
12146
12266
|
const VERSION = appVersion();
|
|
@@ -12429,6 +12549,7 @@ function generateLlmsManifest() {
|
|
|
12429
12549
|
["research claim add \"<text>\"", "refutable assertion"],
|
|
12430
12550
|
["research claim link <cid> <eid> --stance supports|refutes", "link claim to evidence"],
|
|
12431
12551
|
["research claim resolve <cid> --as supported|refuted|contested", "manual arbitration"],
|
|
12552
|
+
["research claim unresolve <cid>", "reset to unverified (undo link auto-promote)"],
|
|
12432
12553
|
["research claim evaluate <cid>", "decision model confidence (0-5 → A/B/C/D)"],
|
|
12433
12554
|
["research evidence assess <eid>", "decision model quality assessment"]
|
|
12434
12555
|
]
|
|
@@ -12593,6 +12714,7 @@ if (require.main === module) main(process.argv.slice(2)).then((rc) => {
|
|
|
12593
12714
|
exports.HOME = HOME;
|
|
12594
12715
|
exports.appVersion = appVersion;
|
|
12595
12716
|
exports.generateLlmsManifest = generateLlmsManifest;
|
|
12717
|
+
exports.groupHelp = groupHelp;
|
|
12596
12718
|
exports.loadBundle = loadBundle;
|
|
12597
12719
|
exports.main = main;
|
|
12598
12720
|
exports.repoRoot = repoRoot;
|
|
@@ -162,7 +162,17 @@ function execute(env, job) {
|
|
|
162
162
|
let output = "";
|
|
163
163
|
const started = Date.now();
|
|
164
164
|
try {
|
|
165
|
-
|
|
165
|
+
// 0.83: the runner process itself holds the app-level write lock; a job
|
|
166
|
+
// command that invokes ygl-trees again would collide with its own parent
|
|
167
|
+
// (self-deadlock — every `ygl-trees …` job failed rc=1 "locked"). Children
|
|
168
|
+
// boot lock-free like webui serve: WAL already permits concurrent readers
|
|
169
|
+
// and serializes writers at the SQLite layer.
|
|
170
|
+
output = (0, node_child_process_1.execSync)(job.command, {
|
|
171
|
+
encoding: "utf8",
|
|
172
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
173
|
+
timeout: 600_000,
|
|
174
|
+
env: { ...process.env, YGL_SERVE: "1" },
|
|
175
|
+
});
|
|
166
176
|
rc = 0;
|
|
167
177
|
}
|
|
168
178
|
catch (e) {
|
|
@@ -72,7 +72,7 @@ function run(env, action, rest, args) {
|
|
|
72
72
|
// -- project resolution (allow --p override) -----------------------------
|
|
73
73
|
let project;
|
|
74
74
|
try {
|
|
75
|
-
project = store.projectResolve(args.flags.p ? String(args.flags.p) : undefined);
|
|
75
|
+
project = store.projectResolve((args.flags.p ?? args.flags.project) ? String(args.flags.p ?? args.flags.project) : undefined);
|
|
76
76
|
}
|
|
77
77
|
catch (e) {
|
|
78
78
|
(0, fmt_1.bad)(ctx, e.message);
|
|
@@ -222,14 +222,32 @@ function run(env, action, rest, args) {
|
|
|
222
222
|
tags: String(args.flags.tags || ""),
|
|
223
223
|
};
|
|
224
224
|
const qRef = String(args.flags.query || "");
|
|
225
|
+
// 0.83 (user report ①): half-write guard — `source add --query X` used
|
|
226
|
+
// to commit the source FIRST and attach separately; a bad query ref
|
|
227
|
+
// left the source landed with a pure error receipt (assets fragment
|
|
228
|
+
// silently). Pre-validate the query ref before ANY write; if the
|
|
229
|
+
// attach still fails post-write, name the landed source + the retry.
|
|
230
|
+
if (qRef) {
|
|
231
|
+
const q = store.db.get("SELECT id FROM queries WHERE (id=? OR text=?) AND project_id=?", [qRef, qRef, project.id]);
|
|
232
|
+
if (!q) {
|
|
233
|
+
(0, fmt_1.bad)(ctx, `query not found: ${(0, text_1.clip)(qRef, 40)} — refusing to write (source add would land without its query link; fix the ref or drop --query)`);
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
225
237
|
return (0, orm_1.getOrm)(env.db.path)
|
|
226
238
|
.then(async (ds) => {
|
|
227
239
|
const r = await writes.sourceAdd(ds, project.id, spec);
|
|
228
|
-
(0, fmt_1.writeResult)(ctx, r, (row) => `source added: ${row.id} ${(0, text_1.clip)(row.title, 60)}`);
|
|
240
|
+
(0, fmt_1.writeResult)(ctx, r, (row) => `source added: ${row.id} ${(0, text_1.clip)(row.title, 60)} · project ${project.name}`);
|
|
229
241
|
if (qRef) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
242
|
+
try {
|
|
243
|
+
const at = await writes.queryAttach(ds, project.id, qRef, r.row.id);
|
|
244
|
+
if (!ctx.json && !at.dup)
|
|
245
|
+
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, ` attached to query: "${(0, text_1.clip)(at.queryText, 40)}"`, ctx.color));
|
|
246
|
+
}
|
|
247
|
+
catch (e) {
|
|
248
|
+
(0, fmt_1.bad)(ctx, `source landed as ${r.row.id} but attach failed: ${e?.message} — research query attach ${(0, text_1.clip)(qRef, 20)} ${r.row.id} to retry`);
|
|
249
|
+
return 1;
|
|
250
|
+
}
|
|
233
251
|
}
|
|
234
252
|
return 0;
|
|
235
253
|
})
|
|
@@ -411,7 +429,7 @@ function run(env, action, rest, args) {
|
|
|
411
429
|
}
|
|
412
430
|
const note = String(args.flags.note || "");
|
|
413
431
|
const locator = String(args.flags.locator || "");
|
|
414
|
-
return runWrite(env, (ds) => writes.evidenceAdd(ds, project.id, sourceId, quote, note, locator, force), (r) => `evidence added: ${r.id} ${(0, text_1.clip)(quote, 50)}`);
|
|
432
|
+
return runWrite(env, (ds) => writes.evidenceAdd(ds, project.id, sourceId, quote, note, locator, force), (r) => `evidence added: ${r.id} ${(0, text_1.clip)(quote, 50)} · project ${project.name}`);
|
|
415
433
|
}
|
|
416
434
|
// R15.3: evidence assess — decision model evaluates evidence quality
|
|
417
435
|
if (action === "evidence" && subAction === "assess") {
|
|
@@ -504,7 +522,7 @@ function run(env, action, rest, args) {
|
|
|
504
522
|
return 2;
|
|
505
523
|
}
|
|
506
524
|
const confidence = String(args.flags.confidence || (0, config_1.configGet)(env.db, "default_confidence"));
|
|
507
|
-
return runWrite(env, (ds) => writes.claimAdd(ds, project.id, text, confidence, force), (r) => `claim added: ${r.id} ${(0, text_1.clip)(text, 60)}`);
|
|
525
|
+
return runWrite(env, (ds) => writes.claimAdd(ds, project.id, text, confidence, force), (r) => `claim added: ${r.id} ${(0, text_1.clip)(text, 60)} · project ${project.name}`);
|
|
508
526
|
}
|
|
509
527
|
if (subAction === "link") {
|
|
510
528
|
if (!rest[1] || !rest[2]) {
|
|
@@ -555,7 +573,17 @@ function run(env, action, rest, args) {
|
|
|
555
573
|
return (0, orm_1.getOrm)(env.db.path)
|
|
556
574
|
.then((ds) => writes.claimLink(ds, rest[1], rest[2], stance))
|
|
557
575
|
.then((r) => {
|
|
558
|
-
|
|
576
|
+
// 0.82.1: prediction claims never auto-promote on link — the receipt
|
|
577
|
+
// says so explicitly instead of showing a silently-unchanged status.
|
|
578
|
+
if (ctx.json) {
|
|
579
|
+
console.log(JSON.stringify({ claim: rest[1], evidence: rest[2], stance, status: r.status, auto_promote: r.auto_promote }, null, 2));
|
|
580
|
+
}
|
|
581
|
+
else if (r.auto_promote === false) {
|
|
582
|
+
(0, fmt_1.ok)(ctx, `linked; prediction claim stays ${r.status} — auto-promote skipped (arbitration: claim resolve / deadline audit)`);
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
(0, fmt_1.ok)(ctx, `linked; claim status now: ${r.status}`);
|
|
586
|
+
}
|
|
559
587
|
// R15 深度集成:链接后自动触发置信度评估(有模型时静默执行)
|
|
560
588
|
try {
|
|
561
589
|
const { evaluateClaimConfidence } = require("../../../ops/tools/src/eval-enhance");
|
|
@@ -629,6 +657,36 @@ function run(env, action, rest, args) {
|
|
|
629
657
|
return 1;
|
|
630
658
|
});
|
|
631
659
|
}
|
|
660
|
+
// 0.82.1 (user report ③): the repair channel for link side effects —
|
|
661
|
+
// resets a claim to unverified. Covers predictions auto-promoted before
|
|
662
|
+
// 0.82.1 AND any claim arbitrated too early (explicit un-arbitration).
|
|
663
|
+
if (subAction === "unresolve") {
|
|
664
|
+
const id = rest[1];
|
|
665
|
+
if (!id) {
|
|
666
|
+
(0, fmt_1.bad)(ctx, "usage: research claim unresolve <claimId>");
|
|
667
|
+
return 2;
|
|
668
|
+
}
|
|
669
|
+
const c0 = store.db.get("SELECT id, status FROM claims WHERE id=? AND project_id=?", [id, project.id]);
|
|
670
|
+
if (!c0) {
|
|
671
|
+
(0, fmt_1.bad)(ctx, `claim not found: ${id}`);
|
|
672
|
+
return 1;
|
|
673
|
+
}
|
|
674
|
+
return (0, orm_1.getOrm)(env.db.path)
|
|
675
|
+
.then((ds) => writes.claimUnresolve(ds, id))
|
|
676
|
+
.then((r) => {
|
|
677
|
+
if (ctx.json)
|
|
678
|
+
console.log(JSON.stringify({ id, status: r.status, duplicate: r.duplicate }, null, 2));
|
|
679
|
+
else
|
|
680
|
+
(0, fmt_1.ok)(ctx, r.duplicate
|
|
681
|
+
? `claim ${id} already unverified (no change)`
|
|
682
|
+
: `claim ${id}: ${c0.status} → unverified (back in tracking; arbitration: claim resolve)`);
|
|
683
|
+
return 0;
|
|
684
|
+
})
|
|
685
|
+
.catch((e) => {
|
|
686
|
+
(0, fmt_1.bad)(ctx, e?.message || String(e));
|
|
687
|
+
return 1;
|
|
688
|
+
});
|
|
689
|
+
}
|
|
632
690
|
// R15.2: claim evaluate — decision model evaluates confidence from linked evidence
|
|
633
691
|
if (subAction === "evaluate") {
|
|
634
692
|
const id = rest[1];
|
|
@@ -5,6 +5,7 @@ exports.sourceAdd = sourceAdd;
|
|
|
5
5
|
exports.evidenceAdd = evidenceAdd;
|
|
6
6
|
exports.claimAdd = claimAdd;
|
|
7
7
|
exports.claimLink = claimLink;
|
|
8
|
+
exports.claimUnresolve = claimUnresolve;
|
|
8
9
|
exports.claimResolve = claimResolve;
|
|
9
10
|
exports.queryAttach = queryAttach;
|
|
10
11
|
exports.kbAdd = kbAdd;
|
|
@@ -178,7 +179,7 @@ async function claimLink(ds, claimId, evidenceId, stance = "supports") {
|
|
|
178
179
|
return ds.transaction(async (mgr) => {
|
|
179
180
|
// Resolve both ends before writing: a bad/foreign id must surface as a
|
|
180
181
|
// named not-found, not as a raw FOREIGN KEY constraint failure.
|
|
181
|
-
const c = (await mgr.query(`SELECT id FROM claims WHERE id=?`, [claimId]));
|
|
182
|
+
const c = (await mgr.query(`SELECT id, text FROM claims WHERE id=?`, [claimId]));
|
|
182
183
|
if (!c.length)
|
|
183
184
|
throw new Error(`claim not found: ${claimId}`);
|
|
184
185
|
const e = (await mgr.query(`SELECT id FROM evidence WHERE id=?`, [evidenceId]));
|
|
@@ -186,6 +187,19 @@ async function claimLink(ds, claimId, evidenceId, stance = "supports") {
|
|
|
186
187
|
throw new Error(`evidence not found: ${evidenceId}`);
|
|
187
188
|
await mgr.query(`INSERT INTO claim_evidence(claim_id,evidence_id,stance) VALUES(?,?,?)
|
|
188
189
|
ON CONFLICT(claim_id,evidence_id) DO UPDATE SET stance=excluded.stance`, [claimId, evidenceId, stance]);
|
|
190
|
+
// 0.82.1 (user report ③): prediction claims (`[预测·…]` prefix) must NOT
|
|
191
|
+
// auto-promote on link — predict audit treats supported/refuted as
|
|
192
|
+
// human-arbitrated (terminal), so a mere evidence link would kick the
|
|
193
|
+
// prediction out of the tracking table before its deadline. Evidence
|
|
194
|
+
// linking is evidence gathering; arbitration stays an explicit
|
|
195
|
+
// `claim resolve` (frozen contract, same discipline as predict audit).
|
|
196
|
+
const text = String(c[0]?.text ?? "");
|
|
197
|
+
if (text.startsWith("[预测·")) {
|
|
198
|
+
const cur = (await mgr.query(`SELECT status FROM claims WHERE id=?`, [claimId]));
|
|
199
|
+
const status = String(cur?.[0]?.status ?? "unverified");
|
|
200
|
+
await logEvent(mgr, "claim.link", "claim", claimId, `${stance} ${evidenceId} (prediction: auto-promote skipped)`);
|
|
201
|
+
return { status, auto_promote: false };
|
|
202
|
+
}
|
|
189
203
|
const sup = (await mgr.query(`SELECT COUNT(*) AS c FROM claim_evidence WHERE claim_id=? AND stance='supports'`, [claimId]));
|
|
190
204
|
const ref = (await mgr.query(`SELECT COUNT(*) AS c FROM claim_evidence WHERE claim_id=? AND stance='refutes'`, [claimId]));
|
|
191
205
|
const s = Number(sup?.[0]?.c ?? 0);
|
|
@@ -193,7 +207,23 @@ async function claimLink(ds, claimId, evidenceId, stance = "supports") {
|
|
|
193
207
|
const status = s && r ? "contested" : s ? "supported" : r ? "refuted" : "unverified";
|
|
194
208
|
await mgr.update(entities_1.Claim, { id: claimId }, { status });
|
|
195
209
|
await logEvent(mgr, "claim.link", "claim", claimId, `${stance} ${evidenceId}`);
|
|
196
|
-
return { status };
|
|
210
|
+
return { status, auto_promote: true };
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/** 0.82.1 (user report ③): reset an auto-promoted / wrongly arbitrated claim
|
|
214
|
+
* back to unverified — the repair channel for link side effects on
|
|
215
|
+
* predictions (and for any claim arbitrated too early). Idempotent: already
|
|
216
|
+
* unverified → duplicate:true, no event spam. */
|
|
217
|
+
async function claimUnresolve(ds, claimId) {
|
|
218
|
+
return ds.transaction(async (mgr) => {
|
|
219
|
+
const c = (await mgr.query(`SELECT id, status FROM claims WHERE id=?`, [claimId]));
|
|
220
|
+
if (!c.length)
|
|
221
|
+
throw new Error(`claim not found: ${claimId}`);
|
|
222
|
+
if (String(c[0]?.status) === "unverified")
|
|
223
|
+
return { status: "unverified", duplicate: true };
|
|
224
|
+
await mgr.update(entities_1.Claim, { id: claimId }, { status: "unverified", updated: (0, text_1.nowIso)() });
|
|
225
|
+
await logEvent(mgr, "claim.unresolve", "claim", claimId, `${c[0]?.status} → unverified`);
|
|
226
|
+
return { status: "unverified", duplicate: false };
|
|
197
227
|
});
|
|
198
228
|
}
|
|
199
229
|
async function claimResolve(ds, claimId, status, note = "") {
|
|
@@ -60,7 +60,7 @@ function run(env, action, rest, args) {
|
|
|
60
60
|
}
|
|
61
61
|
let project;
|
|
62
62
|
try {
|
|
63
|
-
project = store.projectResolve(args.flags.p ? String(args.flags.p) : undefined);
|
|
63
|
+
project = store.projectResolve((args.flags.p ?? args.flags.project) ? String(args.flags.p ?? args.flags.project) : undefined);
|
|
64
64
|
}
|
|
65
65
|
catch (e) {
|
|
66
66
|
(0, fmt_1.bad)(ctx, e.message);
|
|
@@ -204,7 +204,7 @@ function run(env, action, rest, args) {
|
|
|
204
204
|
if (ctx.json)
|
|
205
205
|
console.log(JSON.stringify(row, null, 2));
|
|
206
206
|
else
|
|
207
|
-
(0, fmt_1.ok)(ctx, `task added: ${row.id} ${title}`);
|
|
207
|
+
(0, fmt_1.ok)(ctx, `task added: ${row.id} ${title} · project ${project.name}`);
|
|
208
208
|
return 0;
|
|
209
209
|
}
|
|
210
210
|
// 0.81 ⑦: tasks join the relation fabric — `task add --node` links
|
|
@@ -115,7 +115,7 @@ function run(env, action, rest, args) {
|
|
|
115
115
|
return 0;
|
|
116
116
|
}
|
|
117
117
|
case "show": {
|
|
118
|
-
const resolved = store.projectResolve(args.flags.p ? String(args.flags.p) : undefined);
|
|
118
|
+
const resolved = store.projectResolve((args.flags.p ?? args.flags.project) ? String(args.flags.p ?? args.flags.project) : undefined);
|
|
119
119
|
return (0, orm_1.getOrm)(env.db.path)
|
|
120
120
|
.then(async (ds) => {
|
|
121
121
|
const proj = await ds.getRepository(entities_1.Project).findOne({
|
|
@@ -61,7 +61,14 @@ const text_1 = require("../../../core/text/src/text");
|
|
|
61
61
|
function run(env, action, rest, args) {
|
|
62
62
|
const { ctx, db } = env;
|
|
63
63
|
const trees = new reads_1.TreeStore(db);
|
|
64
|
-
|
|
64
|
+
// 0.83 (user report ①): --tree long alias alongside -t — pointer-hijack
|
|
65
|
+
// pinning needs an ergonomic, self-documenting form. EXCEPTION: `node
|
|
66
|
+
// import --tree <name>` is that command's own target-tree flag (create or
|
|
67
|
+
// merge destination), never a selector — alias must not shadow it.
|
|
68
|
+
const treeFlagAliasOk = !(action === "import");
|
|
69
|
+
const sel = treeFlagAliasOk
|
|
70
|
+
? ((args.flags.t ?? args.flags.tree) ? String(args.flags.t ?? args.flags.tree) : undefined)
|
|
71
|
+
: (args.flags.t ? String(args.flags.t) : undefined);
|
|
65
72
|
const flagNum = (k, d) => {
|
|
66
73
|
const v = Number(args.flags[k]);
|
|
67
74
|
return Number.isFinite(v) ? v : d;
|
|
@@ -548,6 +555,12 @@ function run(env, action, rest, args) {
|
|
|
548
555
|
}
|
|
549
556
|
r.pending = pendingList;
|
|
550
557
|
r.evaluated = evaluated;
|
|
558
|
+
// 0.83 (user report ④): snapshot the rel attachments the eval actually
|
|
559
|
+
// consumed — otherwise a passing P value can't be reproduced without
|
|
560
|
+
// digging the db. Byte-compat: field only when attachments exist.
|
|
561
|
+
const atts = contextPkg.attachments;
|
|
562
|
+
if (Array.isArray(atts) && atts.length)
|
|
563
|
+
r.eval_attachments = atts;
|
|
551
564
|
r.ok = r.missing_required.length === 0 && r.blocked_by.length === 0 && pendingList.length === 0;
|
|
552
565
|
try {
|
|
553
566
|
const run = process.env.YGL_RUN_ID || "";
|
|
@@ -623,6 +636,18 @@ function run(env, action, rest, args) {
|
|
|
623
636
|
}
|
|
624
637
|
for (const e of r.evaluated)
|
|
625
638
|
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, ` eval ${e.name}: P=${e.p ?? "-"} → ${e.outcome}${e.reason ? ` (${e.reason})` : ""}`, ctx.color));
|
|
639
|
+
// 0.83 ④: what the model actually ate this run (rel fabric snapshot)
|
|
640
|
+
if (Array.isArray(r.eval_attachments) && r.eval_attachments.length) {
|
|
641
|
+
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, ` attachments ×${r.eval_attachments.length}:`, ctx.color));
|
|
642
|
+
for (const a of r.eval_attachments) {
|
|
643
|
+
const label = a.type === "claim" ? `${a.id}(${a.status})`
|
|
644
|
+
: a.type === "evidence" ? `${a.id} "${(0, text_1.clip)(a.quote, 36)}"`
|
|
645
|
+
: a.type === "kb" || a.type === "source" ? `${a.id} ${(0, text_1.clip)(a.title, 30)}`
|
|
646
|
+
: a.type === "node" ? `${a.key ?? a.id}`
|
|
647
|
+
: String(a.id);
|
|
648
|
+
(0, fmt_1.out)(ctx, (0, fmt_1.paint)(fmt_1.C.dim, ` - ${a.rel} ← ${a.type}:${label}`, ctx.color));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
626
651
|
if (r.cost_multiplier !== 1)
|
|
627
652
|
(0, fmt_1.out)(ctx, ` 成本系数 ${r.cost_multiplier}`);
|
|
628
653
|
return 0;
|
|
@@ -819,11 +844,11 @@ function run(env, action, rest, args) {
|
|
|
819
844
|
if (sub === "list" || sub === undefined) {
|
|
820
845
|
// Contract ⑤ extension (user report: `node list --tree X` silently
|
|
821
846
|
// swallowed the unknown flag and listed the CURRENT tree). node list
|
|
822
|
-
// takes exactly -t <tree> / --json — any other flag is a typo
|
|
823
|
-
// an explicit rc=2 instead of silent misdirection.
|
|
847
|
+
// takes exactly -t/--tree <tree> / --json — any other flag is a typo
|
|
848
|
+
// worth an explicit rc=2 instead of silent misdirection.
|
|
824
849
|
for (const f of Object.keys(args.flags || {})) {
|
|
825
|
-
if (!["t", "json", "no-color"].includes(f)) {
|
|
826
|
-
(0, fmt_1.bad)(ctx, `unknown flag --${f} for 'node list' — tree selection is '-t <tree>' (positional <tree> also works)`);
|
|
850
|
+
if (!["t", "tree", "json", "no-color"].includes(f)) {
|
|
851
|
+
(0, fmt_1.bad)(ctx, `unknown flag --${f} for 'node list' — tree selection is '-t|--tree <tree>' (positional <tree> also works)`);
|
|
827
852
|
return 2;
|
|
828
853
|
}
|
|
829
854
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ygl-trees",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.83.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
|
"reflect-metadata": "^0.2.2",
|
|
27
27
|
"typeorm": "^1.1.2",
|
|
28
28
|
"@deepseek-ai/cordis": "~4.0.5-alpha.2",
|
|
29
|
-
"@deepseek-ai/cosmokit": "~1.8.6-alpha.2",
|
|
30
29
|
"@deepseek-ai/schemastery": "~3.18.5-alpha.2",
|
|
30
|
+
"@deepseek-ai/cosmokit": "~1.8.6-alpha.2",
|
|
31
31
|
"@deepseek-ai/cordis-plugin-timer": "~1.1.7-alpha.2"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
@@ -66,6 +66,8 @@ ygl-trees node can gate-node --eval --json # evaluated[] 带 type 字段 + p
|
|
|
66
66
|
claim(text+status+confidence)、evidence(quote 截 120 字)、kb/source(标题)、node(key+name)、
|
|
67
67
|
其余类型 id-only。裁决链 `decided-by` 挂好后 eval 模型直接可见,不必 `--state k=v` 手动携带事实
|
|
68
68
|
(用户实测:不挂链 P=0.30,挂链后 0.92)。
|
|
69
|
+
**快照回显(0.83)**:`node can <key> --eval` 的输出把本次吃到的 attachments 打回来——`--json` 的
|
|
70
|
+
`eval_attachments[]` + 人类视图 `attachments ×N:` 逐条;P 值依据可复现,不必翻库。
|
|
69
71
|
|
|
70
72
|
## 5. 凭证与端点(优先级:config > 环境变量/文件 > 默认网关)
|
|
71
73
|
|
|
@@ -67,12 +67,16 @@ ygl-trees webui serve --help # 只打印用法不启服务(0.79.1 短路)
|
|
|
67
67
|
|
|
68
68
|
PG 迁移预检:`db migrate pg` 前自动逐表扫描 INTEGER 列类型——非整数值会**点名 table.column rowid=N** 而非迁移时才爆。
|
|
69
69
|
|
|
70
|
-
## 2b2. 多会话安全(0.79)
|
|
70
|
+
## 2b2. 多会话安全(0.79 → 0.83)
|
|
71
71
|
|
|
72
|
+
- **0.83 并发钉住(最重要)**:并发会话共享 current 指针互踩——写命令直接钉住目标:`research * --project <p>`、`produce/task * --project <p>`、`node * --tree <t>`(`node import --tree` 是目标树名,非选择器);写回执带 `· project <名>` 回显落点
|
|
72
73
|
- `tree use` / `project use` 落 `tree.use` / `project.use` 事件(`YGL_SESSION` 会话标识)
|
|
73
74
|
- `agent brief` 的 `selection_changed`:最近 10 分钟选择变更;跨会话变更置顶提示带 `-t`/`-p`
|
|
74
75
|
- `kb add` 回执自检(`verified:true`)+ 工作区回显(防写错 home 的"丢笔记"事故)
|
|
75
76
|
- `research claim link <cid> <sid> --by-source`:来源下全部证据一次挂齐;误传来源 id 会列出其证据 id
|
|
77
|
+
- `research source add --query` 引用不存在 → 整体拒绝 rc=1 零落地(0.83 半写防护)
|
|
78
|
+
- `--help` 任意深度打组帮助 rc=0(如 `kb add --help`);job 子命令无锁启动(`ygl-trees …` 型任务命令不再与 runner 死锁)
|
|
79
|
+
- 到期巡检范式:`job add "到期巡检" --every 1440 -- ygl-trees node cond --due --days 30`
|
|
76
80
|
|
|
77
81
|
## 2c. DB 锁行为(0.68 → 0.79.1)
|
|
78
82
|
|
|
@@ -34,6 +34,7 @@ ygl-trees research source add --adjudication "会话内裁决:三条件已逐
|
|
|
34
34
|
|
|
35
35
|
来源按 `(project, url_hash)` 去重;`--query` 顺带建立召回关联(query list 显示召回数)。
|
|
36
36
|
`--url` 收任意 `<scheme>://`(https/file/session/a2a/…,0.81)——跨 Agent 引用免落盘。
|
|
37
|
+
**并发钉住(0.83)**:全组接受 `--project <p>` 长旗标(与 `-p` 等价)——并发会话共享 current 指针互踩时直接钉住目标项目,回执带 `· project <名>` 回显落点;`--query` 引用不存在 → 整体拒绝 rc=1 零落地(半写防护)。
|
|
37
38
|
|
|
38
39
|
## 2. 摘录与断言
|
|
39
40
|
|
|
@@ -42,11 +43,13 @@ ygl-trees research evidence add <sourceId> --quote "原文片段" --locator "§2
|
|
|
42
43
|
ygl-trees research evidence add <sourceId> --file quotes.md # 长摘录免转义
|
|
43
44
|
ygl-trees research claim add "断言文本" --confidence A # 档位
|
|
44
45
|
ygl-trees research claim add "断言文本" --confidence 0.92 # 数值档(0.81:0-1 或 0-100%)
|
|
45
|
-
ygl-trees research claim link <claimId> <evidenceId> [--stance refutes] #
|
|
46
|
+
ygl-trees research claim link <claimId> <evidenceId> [--stance refutes] # 状态自动推导(普通断言)
|
|
46
47
|
ygl-trees research claim resolve <claimId> --as contested --note "矛盾待查" # 人工仲裁出口
|
|
48
|
+
ygl-trees research claim unresolve <claimId> # 复位回 unverified(0.82.1:撤销链接副作用/抢跑仲裁)
|
|
47
49
|
```
|
|
48
50
|
|
|
49
51
|
- 有支持有反对 → `contested`;仲裁后新证据链接会重新推导
|
|
52
|
+
- **预测断言(`[预测·…]` 前缀)挂证据不自动晋升**(0.82.1):挂证据 ≠ 仲裁,状态保持 unverified 直到显式 resolve / 到期回测——predict audit 才不会把刚挂证据的预测误判已结案;误晋升用 `claim unresolve` 归位(幂等 duplicate:true)
|
|
50
53
|
- 全部命令幂等:同内容重跑返回已存在行(`duplicate: true`,rc=0)
|
|
51
54
|
|
|
52
55
|
## 3. 产出
|
|
@@ -65,6 +68,7 @@ ygl-trees produce export <reportId> report.md
|
|
|
65
68
|
- 先 `agent brief --json` 看 next_actions(它会告诉你缺来源/缺证据/缺断言)
|
|
66
69
|
- 每条断言至少一条证据再进报告;contested 的要么补证据要么 resolve
|
|
67
70
|
- 预测断言(`[预测·N天]`)定期 `research predict audit` 回测:本地判据自动测;`--state <file|url>`
|
|
68
|
-
桥接外部计数(裸键名后缀匹配 `counts.y_imported`);已 resolve 的默认隐藏(`--all`
|
|
71
|
+
桥接外部计数(裸键名后缀匹配 `counts.y_imported`);已 resolve 的默认隐藏(`--all` 查看终态表,
|
|
72
|
+
`--json` 始终含全量行含终态——机器读全量、人看增量)
|
|
69
73
|
- `--json` 输出可直接 parse 拿 id;列表命令字段:source(id,title,url,relevance,…)、
|
|
70
74
|
claim(id,text,status,confidence,sup,ref)、evidence(id,source,locator,quote,used_by)
|
|
@@ -54,6 +54,8 @@ ygl-trees node unlock mining && ygl-trees node lock mining
|
|
|
54
54
|
- `--state-file` 两种形状:扁平 `{"y_imported":40}` 全节点共享;按节点 `{"a":{"y_imported":40}}` 各取各袋;多键 `--json` 返回数组(0.79.1)
|
|
55
55
|
- `ready --all` / `cond --due` 前置于树解析:无当前树也能跑,跨树巡检一条命令(0.82)
|
|
56
56
|
- `cond --due` 提取 eval 问题文本中的 `YYYY-MM-DD`;默认 7 天窗口(含逾期),`--eval` 判定后仍需人工 resolve/unlock——判定 ≠ 仲裁(0.82)
|
|
57
|
+
- **`--tree <t>` 长旗标(0.83)**:node 组树选择别名(与 `-t` 等价)——并发会话互踩 current_tree 时钉住目标;例外:`node import --tree <name>` 是导入目标树名,不是选择器
|
|
58
|
+
- **`can --eval` attachments 快照(0.83)**:`--json` 增 `eval_attachments[]`、人类视图 `attachments ×N:`——本次判定吃了哪些 rel 证据一目了然,P 值复现不必翻库
|
|
57
59
|
|
|
58
60
|
## 3. 迁移与保真
|
|
59
61
|
|