ygl-trees 0.79.1 → 0.80.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 +5 -0
- package/README.md +4 -1
- package/dist/bundle/bin.cjs +81 -5
- package/dist/packages/research/pipeline/src/cmds.js +25 -4
- package/dist/packages/techtree/model/src/reads.js +51 -1
- package/package.json +1 -1
- package/skills/ygl-trees-eval-gates/SKILL.md +6 -1
- package/skills/ygl-trees-relating/SKILL.md +5 -0
- package/skills/ygl-trees-research/SKILL.md +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.80.0(2026-10-17)rel 织物并入 eval 上下文 + 会话内裁决载体
|
|
4
|
+
- **eval 上下文包并入 rel 挂接摘要**(用户实测发现:裁决已 `decided-by` 挂链,P 仍 0.30,必须 `--state k=v` 手动携带事实才 0.92):`evalContext` 新增 `attachments`——节点挂接的 relations 双向查询(≤12 条),claim 摘录 text+status+confidence、evidence 摘录 quote(截 120 字)、kb/source 标题、node key+name、其余 id-only;无挂接时字段缺省(与旧形状逐字节兼容)
|
|
5
|
+
- **`research source add --adjudication "<裁决摘要>"`**:会话内裁决原生载体——url 自动 `session://adjudication/<yyyymmddhhmm>`,摘要兼作缺省标题;`--url` 校验放宽收 `session://`;裸 `--adjudication` 旗标缺值 rc=2
|
|
6
|
+
- 测试:`packages/techtree/model/tests/evalctx.spec.ts` 5 例(无挂接兼容 / decided-by 摘要 / 反向 evidenced-by / 截断与悬空退化 / 12 条上限);`scripts/readme-verify.sh` +1(--adjudication rc=0)
|
|
7
|
+
|
|
3
8
|
## 0.79.1(2026-10-17)锁身份 + serve --help 短路 + 批量判定 + 回测状态桥
|
|
4
9
|
- **异常①锁归属**:`.ygllock` 记录持有者命令行;撞锁报错、`db unlock`(含 `holder_cmd` JSON 字段)、`doctor` 三处回显真实持有者身份——CLI 撞 CLI 不再被误标为 serve(serve 从不取写锁,报错明示这一点)
|
|
5
10
|
- **异常②**:`webui serve --help` 短路打印用法即退出,不再直接启动服务(随机端口误触发实证)
|
package/README.md
CHANGED
|
@@ -103,7 +103,9 @@ ygl-trees node import --file tree.json --tree copy # 严格校验、事务导
|
|
|
103
103
|
|
|
104
104
|
**批量判定 + `--state-file`(0.79.1)**:`node can k1 k2 k3` 一次进程判定多节点(多键自动进批模式,`--json` 返回数组);`--state-file <json>` 喂状态袋,两种形状——**扁平计数** `{"y_imported": 40}` 全节点共享,或**按节点** `{"a": {"y_imported": 40}, "b": {...}}` 各取各袋。探针脚本从此每树起一次进程而非每节点。
|
|
105
105
|
|
|
106
|
-
**决策模型评估门(spec R8;0.14 布尔 → 0.26 三题型 → 0.27
|
|
106
|
+
**决策模型评估门(spec R8;0.14 布尔 → 0.26 三题型 → 0.27 混批 → 0.80 织物并入)**:语义型必检条件用 eval 题型声明,`node can <key> --eval` 会把**节点元数据 + 直接父(关联)+ 祖先链(闭包索引)+ 条件组 + 已解锁集 + --state 袋 + rel 挂接摘要**组装为上下文包,交给评估模型(TypeSafe jev,经 Vercel AI Gateway)判定:
|
|
107
|
+
|
|
108
|
+
- **rel 织物并入(0.80)**:节点挂接的 evidence(quote 摘要)/ claim(text+status+confidence)/ kb/source(标题)等 rel 关系(双向,≤12 条,文本截 120 字)自动进上下文包的 `attachments`——「证据挂链」纪律从此对语义门槛可见,不必再 `--state k=v` 手动携带裁决事实(实测 P 0.30 → 0.92 的差距由挂链补平)
|
|
107
109
|
|
|
108
110
|
- **三题型**:`--op eval`(布尔 P(true))/ `--op eval-choice`(多选一,判 **P(期望答案)**,`--options "k=desc,..."` + `--answer k`)/ `--op eval-score`(等级评分 `--levels "级0,..."` 2-10 级 + `--min-score n`,判概率加权均值 ±1 灰带)
|
|
109
111
|
- P≥0.7 通过 · P≤0.4 未通过 · **灰带 pending**(过程态,禁作终态,单列不阻塞也不放行);score 的灰带是 [min−1, min)
|
|
@@ -124,6 +126,7 @@ ygl-trees node import --file tree.json --tree copy # 严格校验、事务导
|
|
|
124
126
|
query add 登记检索式
|
|
125
127
|
query attach 检索式 ↔ 来源关联(query list 显示召回数)
|
|
126
128
|
source add 收来源,按 (project, url_hash) 去重;--query 顺带关联
|
|
129
|
+
--adjudication "<裁决摘要>":会话内裁决原生载体(url 自动 session://adjudication/<时刻>,摘要兼作标题);--url 也收 session://
|
|
127
130
|
source fetch <url> 直接抓取网页入库:title/meta description/去标签正文;15s 超时、非 html 拒收、失败零副作用,正文过薄自动打 needs_review
|
|
128
131
|
evidence add 从来源摘原文片段,带 locator(页码/章节);长摘录用 --file <path> 免转义
|
|
129
132
|
claim add 写下断言
|
package/dist/bundle/bin.cjs
CHANGED
|
@@ -2313,6 +2313,12 @@ var TreeStore = class {
|
|
|
2313
2313
|
* + unlocked set + the caller's state bag. The eval API demands a context
|
|
2314
2314
|
* package — bare state fell into the gray band 6/8 in the material's
|
|
2315
2315
|
* routing test, so we ship everything the tree knows about this node.
|
|
2316
|
+
*
|
|
2317
|
+
* 0.80: the rel fabric joins the package — evidence/claim/kb summaries
|
|
2318
|
+
* attached to the node (either direction) land in `attachments`, so a
|
|
2319
|
+
* decided-by chain is visible to the eval model without manually
|
|
2320
|
+
* re-carrying facts via --state (user finding: P 0.30 → 0.92 gap).
|
|
2321
|
+
* Bounded: ≤12 rows, text clipped to 120 chars.
|
|
2316
2322
|
*/
|
|
2317
2323
|
evalContext(treeId, keyOrId, stateBag, context = "default") {
|
|
2318
2324
|
const n = this.nodeGet(treeId, keyOrId);
|
|
@@ -2336,7 +2342,7 @@ var TreeStore = class {
|
|
|
2336
2342
|
effect: c.effect
|
|
2337
2343
|
}));
|
|
2338
2344
|
const unlocked = [...this.unlockedSet(treeId, context)].map((id) => idToKey.get(id) || id);
|
|
2339
|
-
|
|
2345
|
+
const pkg = {
|
|
2340
2346
|
node: {
|
|
2341
2347
|
key: n.key,
|
|
2342
2348
|
name: n.name,
|
|
@@ -2350,6 +2356,62 @@ var TreeStore = class {
|
|
|
2350
2356
|
unlocked,
|
|
2351
2357
|
state: stateBag
|
|
2352
2358
|
};
|
|
2359
|
+
const attachments = this.relAttachments(String(n.id));
|
|
2360
|
+
if (attachments.length) pkg.attachments = attachments;
|
|
2361
|
+
return pkg;
|
|
2362
|
+
}
|
|
2363
|
+
/** 0.80: rel-fabric summaries for the eval context package (both directions). */
|
|
2364
|
+
relAttachments(nodeId) {
|
|
2365
|
+
const rows = this.db.all(`SELECT rel, src_type, src_id, dst_type, dst_id FROM relations
|
|
2366
|
+
WHERE (src_type='node' AND src_id=?) OR (dst_type='node' AND dst_id=?)
|
|
2367
|
+
ORDER BY rowid LIMIT 12`, [nodeId, nodeId]);
|
|
2368
|
+
const clip = (v, n = 120) => String(v ?? "").slice(0, n);
|
|
2369
|
+
return rows.map((r) => {
|
|
2370
|
+
const srcIsMe = r.src_type === "node" && r.src_id === nodeId;
|
|
2371
|
+
const type = String(srcIsMe ? r.dst_type : r.src_type);
|
|
2372
|
+
const id = String(srcIsMe ? r.dst_id : r.src_id);
|
|
2373
|
+
const base = {
|
|
2374
|
+
rel: String(r.rel),
|
|
2375
|
+
type,
|
|
2376
|
+
id
|
|
2377
|
+
};
|
|
2378
|
+
const q = (sql) => this.db.get(sql, [id]);
|
|
2379
|
+
if (type === "claim") {
|
|
2380
|
+
const c = q("SELECT text, status, confidence FROM claims WHERE id=?");
|
|
2381
|
+
if (c) return {
|
|
2382
|
+
...base,
|
|
2383
|
+
text: clip(c.text),
|
|
2384
|
+
status: c.status,
|
|
2385
|
+
confidence: c.confidence
|
|
2386
|
+
};
|
|
2387
|
+
} else if (type === "evidence") {
|
|
2388
|
+
const e = q("SELECT quote FROM evidence WHERE id=?");
|
|
2389
|
+
if (e) return {
|
|
2390
|
+
...base,
|
|
2391
|
+
quote: clip(e.quote)
|
|
2392
|
+
};
|
|
2393
|
+
} else if (type === "kb") {
|
|
2394
|
+
const k = q("SELECT title FROM kb_notes WHERE id=?");
|
|
2395
|
+
if (k) return {
|
|
2396
|
+
...base,
|
|
2397
|
+
title: clip(k.title, 80)
|
|
2398
|
+
};
|
|
2399
|
+
} else if (type === "source") {
|
|
2400
|
+
const s = q("SELECT title FROM sources WHERE id=?");
|
|
2401
|
+
if (s) return {
|
|
2402
|
+
...base,
|
|
2403
|
+
title: clip(s.title, 80)
|
|
2404
|
+
};
|
|
2405
|
+
} else if (type === "node") {
|
|
2406
|
+
const nd = q("SELECT key, name FROM nodes WHERE id=?");
|
|
2407
|
+
if (nd) return {
|
|
2408
|
+
...base,
|
|
2409
|
+
key: nd.key,
|
|
2410
|
+
name: clip(nd.name, 80)
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
return base;
|
|
2414
|
+
});
|
|
2353
2415
|
}
|
|
2354
2416
|
/** Every node whose required gates are currently satisfied. */
|
|
2355
2417
|
ready(treeId, context = "default", state = {}) {
|
|
@@ -6492,6 +6554,10 @@ init_writes();
|
|
|
6492
6554
|
init_entities$1();
|
|
6493
6555
|
/** Run a write through the TypeORM transactional write service and print
|
|
6494
6556
|
* with JSON discipline. Keeps every write command 6 lines. */
|
|
6557
|
+
/** 0.80: compact timestamp for session:// adjudication urls (yyyymmddhhmm). */
|
|
6558
|
+
function nowCompact() {
|
|
6559
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(0, 12);
|
|
6560
|
+
}
|
|
6495
6561
|
function runWrite(env, fn, human) {
|
|
6496
6562
|
const { ctx } = env;
|
|
6497
6563
|
return getOrm(env.db.path).then(fn).then((r) => {
|
|
@@ -6616,14 +6682,24 @@ function run$8(env, action, rest, args) {
|
|
|
6616
6682
|
bad(ctx, "flag needs a value: --title \"...\" / --url \"...\" (bare flag was treated as boolean)");
|
|
6617
6683
|
return 2;
|
|
6618
6684
|
}
|
|
6619
|
-
|
|
6620
|
-
|
|
6685
|
+
let title = String(titleRaw || "");
|
|
6686
|
+
let url = String(urlRaw || "");
|
|
6687
|
+
const adjRaw = args.flags.adjudication;
|
|
6688
|
+
if (adjRaw === true) {
|
|
6689
|
+
bad(ctx, "flag needs a value: --adjudication \"<裁决摘要>\"");
|
|
6690
|
+
return 2;
|
|
6691
|
+
}
|
|
6692
|
+
if (adjRaw !== void 0 && adjRaw !== "") {
|
|
6693
|
+
const adj = String(adjRaw);
|
|
6694
|
+
if (!url) url = `session://adjudication/${nowCompact()}`;
|
|
6695
|
+
if (!title) title = adj.slice(0, 60);
|
|
6696
|
+
}
|
|
6621
6697
|
if (!title && !url) {
|
|
6622
6698
|
bad(ctx, "need --title and/or --url");
|
|
6623
6699
|
return 2;
|
|
6624
6700
|
}
|
|
6625
|
-
if (url && !/^https?:\/\/|^file:\/\//.test(url)) {
|
|
6626
|
-
bad(ctx, `--url must be http(s):// or
|
|
6701
|
+
if (url && !/^https?:\/\/|^file:\/\/|^session:\/\//.test(url)) {
|
|
6702
|
+
bad(ctx, `--url must be http(s)://, file:// or session:// (got: "${require_text.clip(url, 30)}")`);
|
|
6627
6703
|
return 2;
|
|
6628
6704
|
}
|
|
6629
6705
|
const spec = {
|
|
@@ -48,6 +48,10 @@ const config_1 = require("../../../ops/tools/src/config");
|
|
|
48
48
|
const entities_1 = require("./entities");
|
|
49
49
|
/** Run a write through the TypeORM transactional write service and print
|
|
50
50
|
* with JSON discipline. Keeps every write command 6 lines. */
|
|
51
|
+
/** 0.80: compact timestamp for session:// adjudication urls (yyyymmddhhmm). */
|
|
52
|
+
function nowCompact() {
|
|
53
|
+
return new Date().toISOString().replace(/[-:T]/g, "").slice(0, 12);
|
|
54
|
+
}
|
|
51
55
|
function runWrite(env, fn, human) {
|
|
52
56
|
const { ctx } = env;
|
|
53
57
|
return (0, orm_1.getOrm)(env.db.path)
|
|
@@ -177,14 +181,31 @@ function run(env, action, rest, args) {
|
|
|
177
181
|
(0, fmt_1.bad)(ctx, 'flag needs a value: --title "..." / --url "..." (bare flag was treated as boolean)');
|
|
178
182
|
return 2;
|
|
179
183
|
}
|
|
180
|
-
|
|
181
|
-
|
|
184
|
+
let title = String(titleRaw || "");
|
|
185
|
+
let url = String(urlRaw || "");
|
|
186
|
+
// 0.80: in-session adjudication carrier — the most common human
|
|
187
|
+
// arbitration form had no native --url shape (http(s)/file only),
|
|
188
|
+
// forcing temp-file workarounds. `--adjudication "<裁决摘要>"`
|
|
189
|
+
// fills url with a session:// pseudo-scheme and doubles as the
|
|
190
|
+
// title when --title is absent.
|
|
191
|
+
const adjRaw = args.flags.adjudication;
|
|
192
|
+
if (adjRaw === true) {
|
|
193
|
+
(0, fmt_1.bad)(ctx, 'flag needs a value: --adjudication "<裁决摘要>"');
|
|
194
|
+
return 2;
|
|
195
|
+
}
|
|
196
|
+
if (adjRaw !== undefined && adjRaw !== "") {
|
|
197
|
+
const adj = String(adjRaw);
|
|
198
|
+
if (!url)
|
|
199
|
+
url = `session://adjudication/${nowCompact()}`;
|
|
200
|
+
if (!title)
|
|
201
|
+
title = adj.slice(0, 60);
|
|
202
|
+
}
|
|
182
203
|
if (!title && !url) {
|
|
183
204
|
(0, fmt_1.bad)(ctx, "need --title and/or --url");
|
|
184
205
|
return 2;
|
|
185
206
|
}
|
|
186
|
-
if (url && !/^https?:\/\/|^file:\/\//.test(url)) {
|
|
187
|
-
(0, fmt_1.bad)(ctx, `--url must be http(s):// or
|
|
207
|
+
if (url && !/^https?:\/\/|^file:\/\/|^session:\/\//.test(url)) {
|
|
208
|
+
(0, fmt_1.bad)(ctx, `--url must be http(s)://, file:// or session:// (got: "${(0, text_1.clip)(url, 30)}")`);
|
|
188
209
|
return 2;
|
|
189
210
|
}
|
|
190
211
|
const spec = {
|
|
@@ -386,6 +386,12 @@ class TreeStore {
|
|
|
386
386
|
* + unlocked set + the caller's state bag. The eval API demands a context
|
|
387
387
|
* package — bare state fell into the gray band 6/8 in the material's
|
|
388
388
|
* routing test, so we ship everything the tree knows about this node.
|
|
389
|
+
*
|
|
390
|
+
* 0.80: the rel fabric joins the package — evidence/claim/kb summaries
|
|
391
|
+
* attached to the node (either direction) land in `attachments`, so a
|
|
392
|
+
* decided-by chain is visible to the eval model without manually
|
|
393
|
+
* re-carrying facts via --state (user finding: P 0.30 → 0.92 gap).
|
|
394
|
+
* Bounded: ≤12 rows, text clipped to 120 chars.
|
|
389
395
|
*/
|
|
390
396
|
evalContext(treeId, keyOrId, stateBag, context = "default") {
|
|
391
397
|
const n = this.nodeGet(treeId, keyOrId);
|
|
@@ -398,7 +404,7 @@ class TreeStore {
|
|
|
398
404
|
name: c.name, kind: c.kind, op: c.op, value: c.value, required: !!c.required, effect: c.effect,
|
|
399
405
|
}));
|
|
400
406
|
const unlocked = [...this.unlockedSet(treeId, context)].map((id) => idToKey.get(id) || id);
|
|
401
|
-
|
|
407
|
+
const pkg = {
|
|
402
408
|
node: { key: n.key, name: n.name, kind: n.kind, tier: n.tier, cost: n.cost },
|
|
403
409
|
parents,
|
|
404
410
|
ancestors,
|
|
@@ -406,6 +412,50 @@ class TreeStore {
|
|
|
406
412
|
unlocked,
|
|
407
413
|
state: stateBag,
|
|
408
414
|
};
|
|
415
|
+
const attachments = this.relAttachments(String(n.id));
|
|
416
|
+
if (attachments.length)
|
|
417
|
+
pkg.attachments = attachments;
|
|
418
|
+
return pkg;
|
|
419
|
+
}
|
|
420
|
+
/** 0.80: rel-fabric summaries for the eval context package (both directions). */
|
|
421
|
+
relAttachments(nodeId) {
|
|
422
|
+
const rows = this.db.all(`SELECT rel, src_type, src_id, dst_type, dst_id FROM relations
|
|
423
|
+
WHERE (src_type='node' AND src_id=?) OR (dst_type='node' AND dst_id=?)
|
|
424
|
+
ORDER BY rowid LIMIT 12`, [nodeId, nodeId]);
|
|
425
|
+
const clip = (v, n = 120) => String(v ?? "").slice(0, n);
|
|
426
|
+
return rows.map((r) => {
|
|
427
|
+
const srcIsMe = r.src_type === "node" && r.src_id === nodeId;
|
|
428
|
+
const type = String(srcIsMe ? r.dst_type : r.src_type);
|
|
429
|
+
const id = String(srcIsMe ? r.dst_id : r.src_id);
|
|
430
|
+
const base = { rel: String(r.rel), type, id };
|
|
431
|
+
const q = (sql) => this.db.get(sql, [id]);
|
|
432
|
+
if (type === "claim") {
|
|
433
|
+
const c = q("SELECT text, status, confidence FROM claims WHERE id=?");
|
|
434
|
+
if (c)
|
|
435
|
+
return { ...base, text: clip(c.text), status: c.status, confidence: c.confidence };
|
|
436
|
+
}
|
|
437
|
+
else if (type === "evidence") {
|
|
438
|
+
const e = q("SELECT quote FROM evidence WHERE id=?");
|
|
439
|
+
if (e)
|
|
440
|
+
return { ...base, quote: clip(e.quote) };
|
|
441
|
+
}
|
|
442
|
+
else if (type === "kb") {
|
|
443
|
+
const k = q("SELECT title FROM kb_notes WHERE id=?");
|
|
444
|
+
if (k)
|
|
445
|
+
return { ...base, title: clip(k.title, 80) };
|
|
446
|
+
}
|
|
447
|
+
else if (type === "source") {
|
|
448
|
+
const s = q("SELECT title FROM sources WHERE id=?");
|
|
449
|
+
if (s)
|
|
450
|
+
return { ...base, title: clip(s.title, 80) };
|
|
451
|
+
}
|
|
452
|
+
else if (type === "node") {
|
|
453
|
+
const nd = q("SELECT key, name FROM nodes WHERE id=?");
|
|
454
|
+
if (nd)
|
|
455
|
+
return { ...base, key: nd.key, name: clip(nd.name, 80) };
|
|
456
|
+
}
|
|
457
|
+
return base; // query/artifact/skill or dangling ref: id only
|
|
458
|
+
});
|
|
409
459
|
}
|
|
410
460
|
/** Every node whose required gates are currently satisfied. */
|
|
411
461
|
ready(treeId, context = "default", state = {}) {
|
package/package.json
CHANGED
|
@@ -60,7 +60,12 @@ ygl-trees node can gate-node --eval --json # evaluated[] 带 type 字段 + p
|
|
|
60
60
|
## 4. 上下文包(模型看到什么)
|
|
61
61
|
|
|
62
62
|
节点元数据(key/name/kind/tier/cost)+ 直接父(node_edges)+ 祖先链(node_closure)+
|
|
63
|
-
该节点全部条件 + 已解锁集 + `--state`
|
|
63
|
+
该节点全部条件 + 已解锁集 + `--state` 袋 + **rel 挂接摘要(0.80)**。**state 必带上下文包**——裸 state 大概率落灰带。
|
|
64
|
+
|
|
65
|
+
**attachments(0.80 织物并入)**:节点挂接的 rel 关系(双向 ≤12 条)自动摘录入包——
|
|
66
|
+
claim(text+status+confidence)、evidence(quote 截 120 字)、kb/source(标题)、node(key+name)、
|
|
67
|
+
其余类型 id-only。裁决链 `decided-by` 挂好后 eval 模型直接可见,不必 `--state k=v` 手动携带事实
|
|
68
|
+
(用户实测:不挂链 P=0.30,挂链后 0.92)。
|
|
64
69
|
|
|
65
70
|
## 5. 凭证与端点(优先级:config > 环境变量/文件 > 默认网关)
|
|
66
71
|
|
|
@@ -17,6 +17,11 @@ tags: ygl-trees,methodology,relations,fabric
|
|
|
17
17
|
**两条平行流水线**:研究管线(source→evidence→claim→report)和科技树各自发育、零缝合——
|
|
18
18
|
检阅者看不出"这个节点依据什么证据、由什么断言决定、跟踪着什么任务"。`relations` 表就是缝合线。
|
|
19
19
|
|
|
20
|
+
**缝合直接喂决策模型(0.80)**:节点挂接的 rel 关系(双向 ≤12 条)自动进 eval 上下文包
|
|
21
|
+
`attachments`——claim 带 text+status+confidence、evidence 带 quote 摘要。裁决 `decided-by`
|
|
22
|
+
挂链后 `node can --eval` 不必 `--state k=v` 手动携带事实(实测 P 0.30 → 0.92)。
|
|
23
|
+
挂链纪律由此获得语义门槛层面的回报。
|
|
24
|
+
|
|
20
25
|
## 2. rel 词汇表(方向:src --rel→ dst)
|
|
21
26
|
|
|
22
27
|
| rel | 语义 | 典型挂法 |
|
|
@@ -26,9 +26,14 @@ ygl-trees research source add --title t --url https://... \
|
|
|
26
26
|
--abstract "..." --relevance 4 --query "邻接表 vs 闭包表" # 手工入库+关联检索式
|
|
27
27
|
ygl-trees research source fetch https://example.com/page # 自动抓取(title/meta/正文;
|
|
28
28
|
# 非 html 拒收零副作用;薄内容打 needs_review)
|
|
29
|
+
ygl-trees research source add --adjudication "会话内裁决:三条件已逐条核对" \
|
|
30
|
+
--tags adjudication # 会话内裁决原生载体(0.80):
|
|
31
|
+
# url 自动 session://adjudication/<时刻>,
|
|
32
|
+
# 摘要兼作标题——不再需要临时文件绕行
|
|
29
33
|
```
|
|
30
34
|
|
|
31
35
|
来源按 `(project, url_hash)` 去重;`--query` 顺带建立召回关联(query list 显示召回数)。
|
|
36
|
+
`--url` 收 http(s):// · file:// · session://(0.80)。
|
|
32
37
|
|
|
33
38
|
## 2. 摘录与断言
|
|
34
39
|
|