ygl-trees 0.80.0 → 0.81.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 +11 -0
- package/README.md +19 -14
- package/dist/bundle/bin.cjs +771 -632
- package/dist/packages/knowledge/skills/src/cmds.js +27 -0
- package/dist/packages/ops/tools/src/db-cmds.js +29 -4
- package/dist/packages/ops/tools/src/doctor.js +11 -6
- package/dist/packages/ops/tools/src/rels.js +16 -2
- package/dist/packages/research/pipeline/src/cmds.js +18 -4
- package/dist/packages/research/produce/src/produce-cmds.js +23 -8
- package/dist/packages/techtree/model/src/cmds.js +35 -5
- package/dist/packages/techtree/model/src/writes.js +23 -0
- package/package.json +3 -3
- package/skills/ygl-trees-quickstart/SKILL.md +3 -1
- package/skills/ygl-trees-research/SKILL.md +4 -2
- package/skills/ygl-trees-techtree/SKILL.md +4 -3
package/dist/bundle/bin.cjs
CHANGED
|
@@ -5027,6 +5027,19 @@ function run$12(env, action, rest, args) {
|
|
|
5027
5027
|
return 0;
|
|
5028
5028
|
}
|
|
5029
5029
|
case "timeline": {
|
|
5030
|
+
const actorF = String(args.flags.actor || "");
|
|
5031
|
+
const sessionF = String(args.flags.session || "");
|
|
5032
|
+
const conds = [];
|
|
5033
|
+
const params = [];
|
|
5034
|
+
if (actorF) {
|
|
5035
|
+
conds.push("actor=?");
|
|
5036
|
+
params.push(actorF);
|
|
5037
|
+
}
|
|
5038
|
+
if (sessionF) {
|
|
5039
|
+
conds.push(`payload LIKE ?`);
|
|
5040
|
+
params.push(`%"session":"${sessionF}"%`);
|
|
5041
|
+
}
|
|
5042
|
+
const where = conds.length ? ` WHERE ${conds.join(" AND ")}` : "";
|
|
5030
5043
|
const days = db.all(`
|
|
5031
5044
|
SELECT date(ts) as day, COUNT(*) as events,
|
|
5032
5045
|
SUM(CASE WHEN kind LIKE 'project%' THEN 1 ELSE 0 END) as projects,
|
|
@@ -5034,21 +5047,23 @@ function run$12(env, action, rest, args) {
|
|
|
5034
5047
|
SUM(CASE WHEN kind LIKE 'claim%' THEN 1 ELSE 0 END) as claims,
|
|
5035
5048
|
SUM(CASE WHEN kind LIKE 'kb%' THEN 1 ELSE 0 END) as kb,
|
|
5036
5049
|
SUM(CASE WHEN kind LIKE 'node%' THEN 1 ELSE 0 END) as nodes
|
|
5037
|
-
FROM events GROUP BY date(ts) ORDER BY day
|
|
5038
|
-
|
|
5050
|
+
FROM events${where} GROUP BY date(ts) ORDER BY day
|
|
5051
|
+
`, params);
|
|
5039
5052
|
if (!days.length) {
|
|
5040
5053
|
ok(ctx, "no events yet");
|
|
5041
5054
|
return 0;
|
|
5042
5055
|
}
|
|
5056
|
+
const tail = conds.length ? db.all(`SELECT ts, actor, kind, entity_id FROM events${where} ORDER BY ts DESC LIMIT 20`, params) : [];
|
|
5043
5057
|
if (ctx.json) {
|
|
5044
5058
|
console.log(JSON.stringify({
|
|
5045
5059
|
timeline: days,
|
|
5046
|
-
total: days.reduce((s2, d) => s2 + d.events, 0)
|
|
5060
|
+
total: days.reduce((s2, d) => s2 + d.events, 0),
|
|
5061
|
+
...tail.length ? { tail } : {}
|
|
5047
5062
|
}, null, 2));
|
|
5048
5063
|
return 0;
|
|
5049
5064
|
}
|
|
5050
5065
|
const maxEvents = Math.max(...days.map((d) => d.events));
|
|
5051
|
-
head(ctx,
|
|
5066
|
+
head(ctx, `Workspace Timeline${actorF ? ` · actor=${actorF}` : ""}${sessionF ? ` · session=${sessionF}` : ""}`);
|
|
5052
5067
|
let totalP = 0, totalS = 0, totalC = 0, totalK = 0, totalN = 0;
|
|
5053
5068
|
for (const d of days) {
|
|
5054
5069
|
totalP += d.projects;
|
|
@@ -5068,6 +5083,11 @@ function run$12(env, action, rest, args) {
|
|
|
5068
5083
|
}
|
|
5069
5084
|
out(ctx, "");
|
|
5070
5085
|
out(ctx, paint(C.dim, ` 累计: ${totalP}项目 · ${totalS}来源 · ${totalC}断言 · ${totalK}知识 · ${totalN}节点变更`, ctx.color));
|
|
5086
|
+
if (tail.length) {
|
|
5087
|
+
out(ctx, "");
|
|
5088
|
+
out(ctx, paint(C.dim, ` 最近 ${tail.length} 条(按时间倒序):`, ctx.color));
|
|
5089
|
+
for (const t of tail) out(ctx, ` ${String(t.ts).slice(0, 19)} ${String(t.actor).padEnd(12)} ${String(t.kind).padEnd(14)} ${String(t.entity_id || "")}`);
|
|
5090
|
+
}
|
|
5071
5091
|
return 0;
|
|
5072
5092
|
}
|
|
5073
5093
|
case "workspace": {
|
|
@@ -6698,8 +6718,8 @@ function run$8(env, action, rest, args) {
|
|
|
6698
6718
|
bad(ctx, "need --title and/or --url");
|
|
6699
6719
|
return 2;
|
|
6700
6720
|
}
|
|
6701
|
-
if (url && !/^
|
|
6702
|
-
bad(ctx, `--url must be
|
|
6721
|
+
if (url && !/^[a-z][a-z0-9+.-]*:\/\//i.test(url)) {
|
|
6722
|
+
bad(ctx, `--url must be <scheme>:// — https://, file://, session://, a2a:// … (got: "${require_text.clip(url, 30)}")`);
|
|
6703
6723
|
return 2;
|
|
6704
6724
|
}
|
|
6705
6725
|
const spec = {
|
|
@@ -6971,11 +6991,16 @@ function run$8(env, action, rest, args) {
|
|
|
6971
6991
|
if (sub === "claim") {
|
|
6972
6992
|
if (subAction === "add") {
|
|
6973
6993
|
if (rejectBare(ctx, args.flags, ["confidence", "text"])) return 2;
|
|
6974
|
-
|
|
6975
|
-
"
|
|
6976
|
-
"
|
|
6977
|
-
|
|
6978
|
-
|
|
6994
|
+
{
|
|
6995
|
+
const raw = String(args.flags.confidence ?? "");
|
|
6996
|
+
if (raw !== "" && !/^[ABC]$/.test(raw)) {
|
|
6997
|
+
const n = Number(raw.replace(/%$/, ""));
|
|
6998
|
+
if (!Number.isFinite(n) || n < 0 || n > 100) {
|
|
6999
|
+
bad(ctx, `--confidence must be A|B|C or a number 0-1 / 0-100(%) (got: "${require_text.clip(raw, 20)}")`);
|
|
7000
|
+
return 2;
|
|
7001
|
+
}
|
|
7002
|
+
}
|
|
7003
|
+
}
|
|
6979
7004
|
const text = rest.slice(1).join(" ") || String(args.flags.text || "");
|
|
6980
7005
|
if (!text) {
|
|
6981
7006
|
bad(ctx, "missing claim text: research claim add \"<text>\"");
|
|
@@ -7407,151 +7432,480 @@ function run$7(env, action, rest, args) {
|
|
|
7407
7432
|
}
|
|
7408
7433
|
}
|
|
7409
7434
|
//#endregion
|
|
7410
|
-
//#region packages/
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7435
|
+
//#region packages/ops/tools/src/rels.ts
|
|
7436
|
+
var rels_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
7437
|
+
REL_TYPES: () => REL_TYPES,
|
|
7438
|
+
RelError: () => RelError,
|
|
7439
|
+
relAdd: () => relAdd,
|
|
7440
|
+
relRm: () => relRm,
|
|
7441
|
+
resolveId: () => resolveId,
|
|
7442
|
+
resolveRef: () => resolveRef,
|
|
7443
|
+
run: () => run$6
|
|
7444
|
+
});
|
|
7415
7445
|
init_fmt();
|
|
7416
7446
|
require_text.init_text();
|
|
7417
7447
|
init_orm();
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7448
|
+
const REL_TYPES = [
|
|
7449
|
+
"node",
|
|
7450
|
+
"claim",
|
|
7451
|
+
"evidence",
|
|
7452
|
+
"source",
|
|
7453
|
+
"query",
|
|
7454
|
+
"kb",
|
|
7455
|
+
"artifact",
|
|
7456
|
+
"task",
|
|
7457
|
+
"skill"
|
|
7458
|
+
];
|
|
7459
|
+
/** Resolve one `<type>:<ref>` against the database → { type, id, display }.
|
|
7460
|
+
* Throws RelError(code) on unknown type / missing row. */
|
|
7461
|
+
var RelError = class extends Error {
|
|
7462
|
+
code;
|
|
7463
|
+
constructor(code, message) {
|
|
7464
|
+
super(message);
|
|
7465
|
+
this.code = code;
|
|
7466
|
+
}
|
|
7467
|
+
};
|
|
7468
|
+
const DISPLAY = {
|
|
7469
|
+
node: (db, ref) => {
|
|
7470
|
+
const [treeName, key] = ref.includes("/") ? [ref.slice(0, ref.indexOf("/")), ref.slice(ref.indexOf("/") + 1)] : [null, ref];
|
|
7471
|
+
const cur = treeName || db.metaGet("current_tree") || "";
|
|
7472
|
+
if (!cur) throw new RelError(1, `no current tree — qualify as node:<tree>/${key} or run 'ygl-trees tree use <name>' first`);
|
|
7473
|
+
const rows = db.all("SELECT n.id, n.key, n.name FROM nodes n JOIN trees t ON t.id=n.tree_id WHERE (t.name=? OR t.id=?) AND (n.key=? OR n.id=?)", [
|
|
7474
|
+
cur,
|
|
7475
|
+
cur,
|
|
7476
|
+
key,
|
|
7477
|
+
key
|
|
7478
|
+
]);
|
|
7479
|
+
if (!rows.length) throw new RelError(1, `node not found: ${ref}${treeName ? "" : ` (searched current tree '${cur}' — qualify as node:<tree>/${key} for another tree)`}`);
|
|
7480
|
+
return {
|
|
7481
|
+
id: String(rows[0].id),
|
|
7482
|
+
display: String(rows[0].name || rows[0].key)
|
|
7483
|
+
};
|
|
7484
|
+
},
|
|
7485
|
+
claim: (db, ref) => {
|
|
7486
|
+
const r = db.get("SELECT id, text FROM claims WHERE id=? LIMIT 1", [ref]);
|
|
7487
|
+
if (!r) throw new RelError(1, `claim not found: ${ref}`);
|
|
7488
|
+
return {
|
|
7489
|
+
id: String(r.id),
|
|
7490
|
+
display: String(r.text).slice(0, 60)
|
|
7491
|
+
};
|
|
7492
|
+
},
|
|
7493
|
+
evidence: (db, ref) => {
|
|
7494
|
+
const r = db.get("SELECT id, quote FROM evidence WHERE id=? LIMIT 1", [ref]);
|
|
7495
|
+
if (!r) throw new RelError(1, `evidence not found: ${ref}`);
|
|
7496
|
+
return {
|
|
7497
|
+
id: String(r.id),
|
|
7498
|
+
display: String(r.quote).slice(0, 60)
|
|
7499
|
+
};
|
|
7500
|
+
},
|
|
7501
|
+
source: (db, ref) => {
|
|
7502
|
+
const r = db.get("SELECT id, title FROM sources WHERE id=? LIMIT 1", [ref]);
|
|
7503
|
+
if (!r) throw new RelError(1, `source not found: ${ref}`);
|
|
7504
|
+
return {
|
|
7505
|
+
id: String(r.id),
|
|
7506
|
+
display: String(r.title)
|
|
7507
|
+
};
|
|
7508
|
+
},
|
|
7509
|
+
query: (db, ref) => {
|
|
7510
|
+
const r = db.get("SELECT id, text FROM queries WHERE id=? LIMIT 1", [ref]);
|
|
7511
|
+
if (!r) throw new RelError(1, `query not found: ${ref}`);
|
|
7512
|
+
return {
|
|
7513
|
+
id: String(r.id),
|
|
7514
|
+
display: String(r.text).slice(0, 60)
|
|
7515
|
+
};
|
|
7516
|
+
},
|
|
7517
|
+
kb: (db, ref) => {
|
|
7518
|
+
const r = db.get("SELECT id, title FROM kb_notes WHERE id=? OR title=? LIMIT 1", [ref, ref]);
|
|
7519
|
+
if (!r) throw new RelError(1, `kb note not found: ${ref}`);
|
|
7520
|
+
return {
|
|
7521
|
+
id: String(r.id),
|
|
7522
|
+
display: String(r.title)
|
|
7523
|
+
};
|
|
7524
|
+
},
|
|
7525
|
+
artifact: (db, ref) => {
|
|
7526
|
+
const r = db.get("SELECT id, title FROM artifacts WHERE id=? OR title=? LIMIT 1", [ref, ref]);
|
|
7527
|
+
if (!r) throw new RelError(1, `artifact not found: ${ref}`);
|
|
7528
|
+
return {
|
|
7529
|
+
id: String(r.id),
|
|
7530
|
+
display: String(r.title)
|
|
7531
|
+
};
|
|
7532
|
+
},
|
|
7533
|
+
task: (db, ref) => {
|
|
7534
|
+
const r = db.get("SELECT id, title FROM artifacts WHERE kind='task' AND (id=? OR title=?) LIMIT 1", [ref, ref]);
|
|
7535
|
+
if (!r) throw new RelError(1, `task not found: ${ref}`);
|
|
7536
|
+
return {
|
|
7537
|
+
id: String(r.id),
|
|
7538
|
+
display: String(r.title)
|
|
7539
|
+
};
|
|
7540
|
+
},
|
|
7541
|
+
skill: (db, ref) => {
|
|
7542
|
+
const r = db.get("SELECT name, description FROM skills WHERE name=? LIMIT 1", [ref]);
|
|
7543
|
+
if (!r) throw new RelError(1, `skill not found: ${ref}`);
|
|
7544
|
+
return {
|
|
7545
|
+
id: String(r.name),
|
|
7546
|
+
display: String(r.name)
|
|
7428
7547
|
};
|
|
7429
|
-
if (!action || !map[action]) {
|
|
7430
|
-
bad(ctx, "usage: task add \"<title>\" [--note ...] | list [--status todo|doing|done] | todo|doing|done <id>");
|
|
7431
|
-
return 2;
|
|
7432
|
-
}
|
|
7433
|
-
if (map[action] === "task-status") rest = [rest[0], action];
|
|
7434
|
-
action = map[action];
|
|
7435
7548
|
}
|
|
7436
|
-
|
|
7549
|
+
};
|
|
7550
|
+
function resolveRef(env, ref) {
|
|
7551
|
+
const i = ref.indexOf(":");
|
|
7552
|
+
if (i < 1) throw new RelError(2, `bad reference '${ref}' — want <type>:<ref> where type ∈ ${REL_TYPES.join("|")}`);
|
|
7553
|
+
const type = ref.slice(0, i);
|
|
7554
|
+
const ident = ref.slice(i + 1);
|
|
7555
|
+
const fn = DISPLAY[type];
|
|
7556
|
+
if (!fn) throw new RelError(2, `unknown reference type '${type}' — want ${REL_TYPES.join("|")}`);
|
|
7557
|
+
const r = fn(env.db, ident);
|
|
7558
|
+
return {
|
|
7559
|
+
type,
|
|
7560
|
+
id: r.id,
|
|
7561
|
+
display: r.display
|
|
7562
|
+
};
|
|
7563
|
+
}
|
|
7564
|
+
/** Reverse lookup: type+id → display (for the webui related section). */
|
|
7565
|
+
function resolveId(env, type, id) {
|
|
7566
|
+
const fn = DISPLAY[type];
|
|
7567
|
+
if (!fn) return id;
|
|
7437
7568
|
try {
|
|
7438
|
-
|
|
7439
|
-
} catch
|
|
7440
|
-
|
|
7441
|
-
return 1;
|
|
7569
|
+
return fn(env.db, id).display;
|
|
7570
|
+
} catch {
|
|
7571
|
+
return id;
|
|
7442
7572
|
}
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7573
|
+
}
|
|
7574
|
+
async function relAdd(env, srcRef, dstRef, rel = "related") {
|
|
7575
|
+
const src = resolveRef(env, srcRef);
|
|
7576
|
+
const dst = resolveRef(env, dstRef);
|
|
7577
|
+
return (await getOrm(env.db.path)).transaction(async (mgr) => {
|
|
7578
|
+
const ex = await mgr.query(`SELECT id FROM relations WHERE src_type=? AND src_id=? AND dst_type=? AND dst_id=? AND rel=?`, [
|
|
7579
|
+
src.type,
|
|
7580
|
+
src.id,
|
|
7581
|
+
dst.type,
|
|
7582
|
+
dst.id,
|
|
7583
|
+
rel
|
|
7584
|
+
]);
|
|
7585
|
+
if (ex.length) return {
|
|
7586
|
+
row: (await mgr.query(`SELECT * FROM relations WHERE id=?`, [ex[0].id]))[0],
|
|
7587
|
+
duplicate: true
|
|
7588
|
+
};
|
|
7589
|
+
const id = require_text.shortId([
|
|
7590
|
+
"rel",
|
|
7591
|
+
src.id,
|
|
7592
|
+
dst.id,
|
|
7593
|
+
rel
|
|
7594
|
+
], 10);
|
|
7595
|
+
await mgr.query(`INSERT INTO relations(id,src_type,src_id,dst_type,dst_id,rel,weight,created) VALUES(?,?,?,?,?,?,1,?)`, [
|
|
7596
|
+
id,
|
|
7597
|
+
src.type,
|
|
7598
|
+
src.id,
|
|
7599
|
+
dst.type,
|
|
7600
|
+
dst.id,
|
|
7601
|
+
rel,
|
|
7602
|
+
require_text.nowIso()
|
|
7603
|
+
]);
|
|
7604
|
+
await mgr.query(`INSERT INTO events(id,ts,actor,kind,entity,entity_id,payload) VALUES(?,?,?,?,?,?,?)`, [
|
|
7605
|
+
require_text.shortId([
|
|
7606
|
+
"ev",
|
|
7607
|
+
"rel.add",
|
|
7608
|
+
id
|
|
7609
|
+
], 12),
|
|
7610
|
+
require_text.nowIso(),
|
|
7611
|
+
"cli",
|
|
7612
|
+
"rel.add",
|
|
7613
|
+
"relation",
|
|
7614
|
+
id,
|
|
7615
|
+
JSON.stringify({
|
|
7616
|
+
src: `${src.type}:${src.id}`,
|
|
7617
|
+
dst: `${dst.type}:${dst.id}`,
|
|
7618
|
+
rel
|
|
7619
|
+
})
|
|
7620
|
+
]);
|
|
7621
|
+
return {
|
|
7622
|
+
row: (await mgr.query(`SELECT * FROM relations WHERE id=?`, [id]))[0],
|
|
7623
|
+
duplicate: false
|
|
7624
|
+
};
|
|
7625
|
+
});
|
|
7626
|
+
}
|
|
7627
|
+
async function relRm(env, srcRef, dstRef, rel = "related") {
|
|
7628
|
+
const src = resolveRef(env, srcRef);
|
|
7629
|
+
const dst = resolveRef(env, dstRef);
|
|
7630
|
+
await (await getOrm(env.db.path)).transaction(async (mgr) => {
|
|
7631
|
+
if (!(await mgr.query(`SELECT id FROM relations WHERE src_type=? AND src_id=? AND dst_type=? AND dst_id=? AND rel=?`, [
|
|
7632
|
+
src.type,
|
|
7633
|
+
src.id,
|
|
7634
|
+
dst.type,
|
|
7635
|
+
dst.id,
|
|
7636
|
+
rel
|
|
7637
|
+
])).length) throw new RelError(1, `relation not found: ${srcRef} → ${dstRef} (${rel})`);
|
|
7638
|
+
await mgr.query(`DELETE FROM relations WHERE src_type=? AND src_id=? AND dst_type=? AND dst_id=? AND rel=?`, [
|
|
7639
|
+
src.type,
|
|
7640
|
+
src.id,
|
|
7641
|
+
dst.type,
|
|
7642
|
+
dst.id,
|
|
7643
|
+
rel
|
|
7644
|
+
]);
|
|
7645
|
+
});
|
|
7646
|
+
}
|
|
7647
|
+
function run$6(env, action, rest, args) {
|
|
7648
|
+
const { ctx } = env;
|
|
7649
|
+
if (action === "add") {
|
|
7650
|
+
const [a, b] = rest;
|
|
7651
|
+
if (!a || !b) {
|
|
7652
|
+
bad(ctx, "usage: rel add <type>:<ref> <type>:<ref> [--rel <type>]");
|
|
7653
|
+
return 2;
|
|
7654
|
+
}
|
|
7655
|
+
const rel = String(args.flags.rel || "related");
|
|
7656
|
+
return (async () => {
|
|
7657
|
+
try {
|
|
7658
|
+
const r = await relAdd(env, a, b, rel);
|
|
7659
|
+
if (ctx.json) {
|
|
7660
|
+
console.log(JSON.stringify({
|
|
7661
|
+
id: r.row.id,
|
|
7662
|
+
src: `${r.row.src_type}:${r.row.src_id}`,
|
|
7663
|
+
dst: `${r.row.dst_type}:${r.row.dst_id}`,
|
|
7664
|
+
rel: r.row.rel,
|
|
7665
|
+
duplicate: r.duplicate
|
|
7666
|
+
}));
|
|
7667
|
+
return 0;
|
|
7668
|
+
}
|
|
7669
|
+
if (r.duplicate) out(ctx, paint(C.yellow, ` ⚠ duplicate: ${a} ${rel} ${b} (already linked)`, ctx.color));
|
|
7670
|
+
else ok(ctx, `linked: ${a} ${paint(C.dim, `—${rel}→`, ctx.color)} ${b}`);
|
|
7671
|
+
return 0;
|
|
7672
|
+
} catch (e) {
|
|
7673
|
+
if (e instanceof RelError) {
|
|
7674
|
+
bad(ctx, e.message);
|
|
7675
|
+
return e.code;
|
|
7676
|
+
}
|
|
7677
|
+
throw e;
|
|
7678
|
+
}
|
|
7679
|
+
})();
|
|
7680
|
+
}
|
|
7681
|
+
if (action === "rm") {
|
|
7682
|
+
const [a, b] = rest;
|
|
7683
|
+
if (!a || !b) {
|
|
7684
|
+
bad(ctx, "usage: rel rm <type>:<ref> <type>:<ref> [--rel <type>]");
|
|
7685
|
+
return 2;
|
|
7686
|
+
}
|
|
7687
|
+
const rel = String(args.flags.rel || "related");
|
|
7688
|
+
return (async () => {
|
|
7689
|
+
try {
|
|
7690
|
+
await relRm(env, a, b, rel);
|
|
7691
|
+
if (ctx.json) console.log(JSON.stringify({ removed: `${a} -${rel}-> ${b}` }));
|
|
7692
|
+
else ok(ctx, `unlinked: ${a} (${rel}) ${b}`);
|
|
7693
|
+
return 0;
|
|
7694
|
+
} catch (e) {
|
|
7695
|
+
if (e instanceof RelError) {
|
|
7696
|
+
bad(ctx, e.message);
|
|
7697
|
+
return e.code;
|
|
7698
|
+
}
|
|
7699
|
+
throw e;
|
|
7700
|
+
}
|
|
7701
|
+
})();
|
|
7702
|
+
}
|
|
7703
|
+
if (action === "list" || action === "") {
|
|
7704
|
+
const outRows = (() => {
|
|
7705
|
+
const params = [];
|
|
7706
|
+
let where = "1=1";
|
|
7707
|
+
const srcF = String(args.flags.src || "");
|
|
7708
|
+
const dstF = String(args.flags.dst || "");
|
|
7709
|
+
const relF = String(args.flags.rel || "");
|
|
7710
|
+
if (srcF) {
|
|
7711
|
+
const s = resolveRef(env, srcF);
|
|
7712
|
+
where += " AND src_type=? AND src_id=?";
|
|
7713
|
+
params.push(s.type, s.id);
|
|
7714
|
+
}
|
|
7715
|
+
if (dstF) {
|
|
7716
|
+
const d = resolveRef(env, dstF);
|
|
7717
|
+
where += " AND dst_type=? AND dst_id=?";
|
|
7718
|
+
params.push(d.type, d.id);
|
|
7719
|
+
}
|
|
7720
|
+
if (relF) {
|
|
7721
|
+
where += " AND rel=?";
|
|
7722
|
+
params.push(relF);
|
|
7723
|
+
}
|
|
7724
|
+
return env.db.all(`SELECT * FROM relations WHERE ${where} ORDER BY rowid DESC LIMIT 200`, params);
|
|
7725
|
+
})().map((r) => ({
|
|
7726
|
+
src: `${r.src_type}:${r.src_id}`,
|
|
7727
|
+
dst: `${r.dst_type}:${r.dst_id}`,
|
|
7728
|
+
rel: r.rel,
|
|
7729
|
+
src_display: resolveId(env, r.src_type, r.src_id),
|
|
7730
|
+
dst_display: resolveId(env, r.dst_type, r.dst_id)
|
|
7731
|
+
}));
|
|
7732
|
+
if (ctx.json) {
|
|
7733
|
+
console.log(JSON.stringify(outRows, null, 2));
|
|
7734
|
+
return 0;
|
|
7735
|
+
}
|
|
7736
|
+
head(ctx, `Relations (${outRows.length})`);
|
|
7737
|
+
table(ctx, outRows, [
|
|
7738
|
+
"src",
|
|
7739
|
+
"rel",
|
|
7740
|
+
"dst"
|
|
7741
|
+
], [
|
|
7742
|
+
"src_display",
|
|
7743
|
+
"rel",
|
|
7744
|
+
"dst_display"
|
|
7745
|
+
]);
|
|
7746
|
+
return 0;
|
|
7747
|
+
}
|
|
7748
|
+
bad(ctx, "usage: rel [add|rm|list] ... (see ygl-trees help)");
|
|
7749
|
+
return 2;
|
|
7750
|
+
}
|
|
7751
|
+
//#endregion
|
|
7752
|
+
//#region packages/research/produce/src/produce-cmds.ts
|
|
7753
|
+
/** produce group — evidence-backed outline → sections → report.
|
|
7754
|
+
* Writes run on the TypeORM write service (src/writes.ts); reads stay on
|
|
7755
|
+
* Store until their own migration batch. */
|
|
7756
|
+
var produce_cmds_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$5 });
|
|
7757
|
+
init_fmt();
|
|
7758
|
+
require_text.init_text();
|
|
7759
|
+
init_orm();
|
|
7760
|
+
init_writes();
|
|
7761
|
+
function run$5(env, action, rest, args) {
|
|
7762
|
+
const { ctx, store } = env;
|
|
7763
|
+
if (env.group === "task") {
|
|
7764
|
+
const map = {
|
|
7765
|
+
add: "task-add",
|
|
7766
|
+
list: "task-list",
|
|
7767
|
+
doing: "task-status",
|
|
7768
|
+
done: "task-status",
|
|
7769
|
+
todo: "task-status"
|
|
7770
|
+
};
|
|
7771
|
+
if (!action || !map[action]) {
|
|
7772
|
+
bad(ctx, "usage: task add \"<title>\" [--note ...] | list [--status todo|doing|done] | todo|doing|done <id>");
|
|
7773
|
+
return 2;
|
|
7774
|
+
}
|
|
7775
|
+
if (map[action] === "task-status") rest = [rest[0], action];
|
|
7776
|
+
action = map[action];
|
|
7777
|
+
}
|
|
7778
|
+
let project;
|
|
7779
|
+
try {
|
|
7780
|
+
project = store.projectResolve(args.flags.p ? String(args.flags.p) : void 0);
|
|
7781
|
+
} catch (e) {
|
|
7782
|
+
bad(ctx, e.message);
|
|
7783
|
+
return 1;
|
|
7784
|
+
}
|
|
7785
|
+
const pid = project.id;
|
|
7786
|
+
const readBody = () => {
|
|
7787
|
+
if (args.flags.body) return String(args.flags.body);
|
|
7788
|
+
if (args.flags.f) return node_fs.readFileSync(String(args.flags.f), "utf8");
|
|
7789
|
+
return "";
|
|
7790
|
+
};
|
|
7791
|
+
switch (action) {
|
|
7792
|
+
case "outline": {
|
|
7793
|
+
const claims = store.claimList(pid);
|
|
7794
|
+
const lines = [`# ${project.name}`, ""];
|
|
7795
|
+
if (project.question) lines.push(`> ${project.question}`, "");
|
|
7796
|
+
lines.push("## 结论要点", "");
|
|
7797
|
+
if (!claims.length) lines.push("_(尚无 claim,先跑 research claim add)_", "");
|
|
7798
|
+
claims.forEach((c, i) => {
|
|
7799
|
+
const mark = c.status === "supported" ? "[已证实]" : c.status === "refuted" ? "[已推翻]" : c.status === "contested" ? "[存争议]" : "[待验证]";
|
|
7800
|
+
lines.push(`${i + 1}. ${mark} ${c.text}`);
|
|
7801
|
+
});
|
|
7802
|
+
lines.push("", "## 证据基础", "");
|
|
7803
|
+
const ev = store.evidenceList(pid);
|
|
7804
|
+
if (!ev.length) lines.push("_(尚无 evidence)_", "");
|
|
7805
|
+
ev.forEach((e, i) => lines.push(`- [${i + 1}] ${e.source}:${require_text.clip(e.quote, 80)}`));
|
|
7806
|
+
const content = lines.join("\n");
|
|
7807
|
+
return getOrm(env.db.path).then((ds) => artifactAdd(ds, pid, "outline", `${project.name} outline`, content)).then((r) => {
|
|
7808
|
+
if (ctx.json) console.log(JSON.stringify(r.row, null, 2));
|
|
7809
|
+
else {
|
|
7810
|
+
ok(ctx, `outline generated: ${r.row.id} (v${r.version})`);
|
|
7811
|
+
out(ctx, content);
|
|
7812
|
+
}
|
|
7813
|
+
return 0;
|
|
7814
|
+
}).catch((e) => {
|
|
7815
|
+
bad(ctx, e?.message || String(e));
|
|
7535
7816
|
return 1;
|
|
7536
7817
|
});
|
|
7537
7818
|
}
|
|
7538
|
-
case "
|
|
7539
|
-
|
|
7540
|
-
const
|
|
7541
|
-
if (!title) {
|
|
7542
|
-
bad(ctx, "usage:
|
|
7819
|
+
case "section": {
|
|
7820
|
+
const title = rest[0] || String(args.flags.title || "");
|
|
7821
|
+
const body = readBody();
|
|
7822
|
+
if (!title || !body) {
|
|
7823
|
+
bad(ctx, "usage: produce section <title> --body|-f <file>");
|
|
7543
7824
|
return 2;
|
|
7544
7825
|
}
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
else ok(ctx, `task added: ${row.id} ${title}`);
|
|
7826
|
+
return getOrm(env.db.path).then((ds) => artifactAdd(ds, pid, "section", title, body)).then((r) => {
|
|
7827
|
+
if (ctx.json) console.log(JSON.stringify(r.row, null, 2));
|
|
7828
|
+
else ok(ctx, `section saved: ${r.row.id} (v${r.version})`);
|
|
7549
7829
|
return 0;
|
|
7550
7830
|
}).catch((e) => {
|
|
7551
7831
|
bad(ctx, e?.message || String(e));
|
|
7552
7832
|
return 1;
|
|
7553
7833
|
});
|
|
7554
7834
|
}
|
|
7835
|
+
case "spec": {
|
|
7836
|
+
const title = rest[0] || String(args.flags.title || "");
|
|
7837
|
+
const body = readBody();
|
|
7838
|
+
if (args.flags["design-type"] !== void 0) {
|
|
7839
|
+
bad(ctx, "--design-type belongs to 'produce design' (spec does not take it; if this is a design decision, use: produce design <title> --design-type decision)");
|
|
7840
|
+
return 2;
|
|
7841
|
+
}
|
|
7842
|
+
if (!title || !body) {
|
|
7843
|
+
bad(ctx, "usage: produce spec <title> --body|-f <file>");
|
|
7844
|
+
return 2;
|
|
7845
|
+
}
|
|
7846
|
+
return getOrm(env.db.path).then((ds) => artifactAdd(ds, pid, "spec", title, body)).then((r) => {
|
|
7847
|
+
if (ctx.json) console.log(JSON.stringify(r.row, null, 2));
|
|
7848
|
+
else ok(ctx, `spec saved: ${r.row.id} (v${r.version})`);
|
|
7849
|
+
return 0;
|
|
7850
|
+
}).catch((e) => {
|
|
7851
|
+
bad(ctx, e?.message || String(e));
|
|
7852
|
+
return 1;
|
|
7853
|
+
});
|
|
7854
|
+
}
|
|
7855
|
+
case "design": {
|
|
7856
|
+
const title = rest[0] || String(args.flags.title || "");
|
|
7857
|
+
const body = readBody();
|
|
7858
|
+
const designType = String(args.flags["design-type"] || "arch");
|
|
7859
|
+
if (!title || !body) {
|
|
7860
|
+
bad(ctx, "usage: produce design <title> --body|-f <file> [--design-type ui-ux|arch|decision]");
|
|
7861
|
+
return 2;
|
|
7862
|
+
}
|
|
7863
|
+
if (![
|
|
7864
|
+
"ui-ux",
|
|
7865
|
+
"arch",
|
|
7866
|
+
"decision"
|
|
7867
|
+
].includes(designType)) {
|
|
7868
|
+
bad(ctx, "--design-type must be one of ui-ux|arch|decision");
|
|
7869
|
+
return 2;
|
|
7870
|
+
}
|
|
7871
|
+
return getOrm(env.db.path).then((ds) => artifactAdd(ds, pid, "design", title, body, "markdown", JSON.stringify({ design_type: designType }))).then((r) => {
|
|
7872
|
+
if (ctx.json) console.log(JSON.stringify(r.row, null, 2));
|
|
7873
|
+
else ok(ctx, `design saved: ${r.row.id} (v${r.version}) [${designType}]`);
|
|
7874
|
+
return 0;
|
|
7875
|
+
}).catch((e) => {
|
|
7876
|
+
bad(ctx, e?.message || String(e));
|
|
7877
|
+
return 1;
|
|
7878
|
+
});
|
|
7879
|
+
}
|
|
7880
|
+
case "task-add": {
|
|
7881
|
+
if (rejectBare(ctx, args.flags, ["note", "node"])) return 2;
|
|
7882
|
+
const title = rest.join(" ");
|
|
7883
|
+
if (!title) {
|
|
7884
|
+
bad(ctx, "usage: task add \"<title>\" [--note ...] [--node <tree>/<key>]");
|
|
7885
|
+
return 2;
|
|
7886
|
+
}
|
|
7887
|
+
const note = String(args.flags.note || "");
|
|
7888
|
+
const nodeRef = args.flags.node !== void 0 ? String(args.flags.node) : "";
|
|
7889
|
+
return getOrm(env.db.path).then((ds) => taskAdd(ds, pid, title, note)).then((row) => {
|
|
7890
|
+
if (!nodeRef) {
|
|
7891
|
+
if (ctx.json) console.log(JSON.stringify(row, null, 2));
|
|
7892
|
+
else ok(ctx, `task added: ${row.id} ${title}`);
|
|
7893
|
+
return 0;
|
|
7894
|
+
}
|
|
7895
|
+
return relAdd(env, `node:${nodeRef}`, `task:${row.id}`, "tracked-by").then((r) => {
|
|
7896
|
+
if (ctx.json) console.log(JSON.stringify({
|
|
7897
|
+
...row,
|
|
7898
|
+
node: nodeRef,
|
|
7899
|
+
relation: "tracked-by"
|
|
7900
|
+
}, null, 2));
|
|
7901
|
+
else ok(ctx, `task added: ${row.id} ${title} (tracked-by node:${nodeRef})`);
|
|
7902
|
+
return 0;
|
|
7903
|
+
});
|
|
7904
|
+
}).catch((e) => {
|
|
7905
|
+
bad(ctx, e?.message || String(e));
|
|
7906
|
+
return e instanceof RelError && e.code === 2 ? 2 : 1;
|
|
7907
|
+
});
|
|
7908
|
+
}
|
|
7555
7909
|
case "task-list": {
|
|
7556
7910
|
const rows = store.db.all("SELECT id, title, meta, created FROM artifacts WHERE project_id=? AND kind='task' ORDER BY created DESC", [pid]).map((r) => {
|
|
7557
7911
|
let meta = {};
|
|
@@ -8086,6 +8440,41 @@ async function condAdd(ds, treeId, keyOrId, o) {
|
|
|
8086
8440
|
return (await mgr.query(`SELECT * FROM node_conditions WHERE id=?`, [id]))[0];
|
|
8087
8441
|
});
|
|
8088
8442
|
}
|
|
8443
|
+
/** 0.81: `node cond rm <key> --name <n>` — remove one condition by natural
|
|
8444
|
+
* key. Throws "node not found" / "condition not found: <name> (node X has:
|
|
8445
|
+
* ...)" (named, per frozen contract ④). Mirrors condAdd: transactional +
|
|
8446
|
+
* event (node.cond-rm) + markDirty. */
|
|
8447
|
+
async function condRemove(ds, treeId, keyOrId, name) {
|
|
8448
|
+
return ds.transaction(async (mgr) => {
|
|
8449
|
+
const n = await mgr.query(`SELECT id FROM nodes WHERE tree_id=? AND (key=? OR id=?)`, [
|
|
8450
|
+
treeId,
|
|
8451
|
+
keyOrId,
|
|
8452
|
+
keyOrId
|
|
8453
|
+
]);
|
|
8454
|
+
if (!n.length) throw new Error(`node not found: ${keyOrId}`);
|
|
8455
|
+
const existing = await mgr.query(`SELECT * FROM node_conditions WHERE node_id=? AND name=?`, [n[0].id, name]);
|
|
8456
|
+
if (!existing.length) {
|
|
8457
|
+
const have = (await mgr.query(`SELECT name FROM node_conditions WHERE node_id=?`, [n[0].id])).map((r) => r.name).join(", ") || "(none)";
|
|
8458
|
+
throw new Error(`condition not found: ${name} (node ${keyOrId} has: ${have})`);
|
|
8459
|
+
}
|
|
8460
|
+
const row = existing[0];
|
|
8461
|
+
await mgr.query(`DELETE FROM node_conditions WHERE id=?`, [row.id]);
|
|
8462
|
+
await markDirty(mgr, treeId);
|
|
8463
|
+
await logEvent(mgr, "node.cond-rm", row.id, {
|
|
8464
|
+
tree: treeId,
|
|
8465
|
+
node: keyOrId,
|
|
8466
|
+
name,
|
|
8467
|
+
kind: row.kind,
|
|
8468
|
+
op: row.op,
|
|
8469
|
+
required: !!row.required,
|
|
8470
|
+
effect: row.effect
|
|
8471
|
+
});
|
|
8472
|
+
return {
|
|
8473
|
+
...row,
|
|
8474
|
+
removed: true
|
|
8475
|
+
};
|
|
8476
|
+
});
|
|
8477
|
+
}
|
|
8089
8478
|
async function stateSet(ds, treeId, keyOrId, status, context = "default", level = 0) {
|
|
8090
8479
|
return ds.transaction(async (mgr) => {
|
|
8091
8480
|
const n = await mgr.query(`SELECT id FROM nodes WHERE tree_id=? AND (key=? OR id=?)`, [
|
|
@@ -8316,12 +8705,12 @@ async function importTree(ds, spec, opts = {}) {
|
|
|
8316
8705
|
* ygl-trees node validate sanity checks
|
|
8317
8706
|
* ygl-trees node export [--out file] portable JSON
|
|
8318
8707
|
*/
|
|
8319
|
-
var cmds_exports$1 = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$
|
|
8708
|
+
var cmds_exports$1 = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$4 });
|
|
8320
8709
|
init_fmt();
|
|
8321
8710
|
init_orm();
|
|
8322
8711
|
init_eval();
|
|
8323
8712
|
require_text.init_text();
|
|
8324
|
-
function run$
|
|
8713
|
+
function run$4(env, action, rest, args) {
|
|
8325
8714
|
const { ctx, db } = env;
|
|
8326
8715
|
const trees = new TreeStore(db);
|
|
8327
8716
|
const sel = args.flags.t ? String(args.flags.t) : void 0;
|
|
@@ -8505,10 +8894,25 @@ function run$5(env, action, rest, args) {
|
|
|
8505
8894
|
});
|
|
8506
8895
|
}
|
|
8507
8896
|
if (sub === "cond") {
|
|
8508
|
-
|
|
8509
|
-
|
|
8897
|
+
let cKey = key;
|
|
8898
|
+
const cAct = key === "add" ? "add" : key === "rm" || key === "remove" ? "rm" : "";
|
|
8899
|
+
if (cAct) cKey = rest[1];
|
|
8900
|
+
if (cAct === "rm") {
|
|
8901
|
+
const name = flagStr("name");
|
|
8902
|
+
if (!cKey || !name) {
|
|
8903
|
+
bad(ctx, "usage: node cond rm <key> --name <cond>");
|
|
8904
|
+
return 2;
|
|
8905
|
+
}
|
|
8906
|
+
return runTreeWrite((ds) => condRemove(ds, tid, cKey, name), (r) => {
|
|
8907
|
+
ok(ctx, `condition removed: ${cKey} · ${name} (was ${r.required ? "必检" : "可选"}/${r.effect})`);
|
|
8908
|
+
return 0;
|
|
8909
|
+
});
|
|
8910
|
+
}
|
|
8911
|
+
if (!cKey) {
|
|
8912
|
+
bad(ctx, "usage: node cond [add|rm] <key> --name <cond> [--kind ..] [--op >=|==|<=|!=|has] [--value n] [--op eval [--value q] | eval-choice --options k=d,.. --answer k | eval-score --levels a,b,.. --min-score n] [--optional] [--effect gate|cost_mul] [--weight n]");
|
|
8510
8913
|
return 2;
|
|
8511
8914
|
}
|
|
8915
|
+
const k = cKey;
|
|
8512
8916
|
if (rejectBare(ctx, args.flags, [
|
|
8513
8917
|
"name",
|
|
8514
8918
|
"value",
|
|
@@ -8533,7 +8937,7 @@ function run$5(env, action, rest, args) {
|
|
|
8533
8937
|
if (validateEnum(ctx, args.flags, "effect", ["gate", "cost_mul"])) return 2;
|
|
8534
8938
|
const name = flagStr("name");
|
|
8535
8939
|
if (!name) {
|
|
8536
|
-
table(ctx, trees.condList(tid,
|
|
8940
|
+
table(ctx, trees.condList(tid, k), [
|
|
8537
8941
|
"id",
|
|
8538
8942
|
"name",
|
|
8539
8943
|
"kind",
|
|
@@ -8589,7 +8993,7 @@ function run$5(env, action, rest, args) {
|
|
|
8589
8993
|
value = packEvalScoreSpec(q, levels, min);
|
|
8590
8994
|
}
|
|
8591
8995
|
}
|
|
8592
|
-
return runTreeWrite((ds) => condAdd(ds, tid,
|
|
8996
|
+
return runTreeWrite((ds) => condAdd(ds, tid, k, {
|
|
8593
8997
|
name,
|
|
8594
8998
|
kind: flagStr("kind", "flag"),
|
|
8595
8999
|
expr: flagStr("expr") || void 0,
|
|
@@ -8599,7 +9003,7 @@ function run$5(env, action, rest, args) {
|
|
|
8599
9003
|
effect: flagStr("effect", "gate"),
|
|
8600
9004
|
weight: flagNum("weight", 1)
|
|
8601
9005
|
}), (c) => {
|
|
8602
|
-
ack(ctx, c, `condition added: ${
|
|
9006
|
+
ack(ctx, c, `condition added: ${k} · ${c.name} [${c.required ? "必检" : "可选"}/${c.effect}]`);
|
|
8603
9007
|
return 0;
|
|
8604
9008
|
});
|
|
8605
9009
|
}
|
|
@@ -9081,6 +9485,14 @@ function run$5(env, action, rest, args) {
|
|
|
9081
9485
|
return 0;
|
|
9082
9486
|
}
|
|
9083
9487
|
if (sub === "list" || sub === void 0) {
|
|
9488
|
+
for (const f of Object.keys(args.flags || {})) if (![
|
|
9489
|
+
"t",
|
|
9490
|
+
"json",
|
|
9491
|
+
"no-color"
|
|
9492
|
+
].includes(f)) {
|
|
9493
|
+
bad(ctx, `unknown flag --${f} for 'node list' — tree selection is '-t <tree>' (positional <tree> also works)`);
|
|
9494
|
+
return 2;
|
|
9495
|
+
}
|
|
9084
9496
|
table(ctx, trees.nodeList(tid), [
|
|
9085
9497
|
"key",
|
|
9086
9498
|
"name",
|
|
@@ -9311,12 +9723,12 @@ function loadStateFile(args, shared) {
|
|
|
9311
9723
|
//#region packages/knowledge/skills/src/skill-cmds.ts
|
|
9312
9724
|
/** skill group — the L1/L2/L3 registry agents query before acting.
|
|
9313
9725
|
* Writes run on the TypeORM write service (src/writes.ts). */
|
|
9314
|
-
var skill_cmds_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$
|
|
9726
|
+
var skill_cmds_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$3 });
|
|
9315
9727
|
init_fmt();
|
|
9316
9728
|
require_text.init_text();
|
|
9317
9729
|
init_orm();
|
|
9318
9730
|
init_writes();
|
|
9319
|
-
function run$
|
|
9731
|
+
function run$3(env, action, rest, args) {
|
|
9320
9732
|
const { ctx, store } = env;
|
|
9321
9733
|
const name = rest[0];
|
|
9322
9734
|
const need = (v, what) => {
|
|
@@ -9418,7 +9830,7 @@ function run$4(env, action, rest, args) {
|
|
|
9418
9830
|
case "add": {
|
|
9419
9831
|
const n = args.flags.name ? String(args.flags.name) : "";
|
|
9420
9832
|
const file = args.flags.f ? String(args.flags.f) : "";
|
|
9421
|
-
if (file) return run$
|
|
9833
|
+
if (file) return run$3(env, "import", [file], args);
|
|
9422
9834
|
if (!need(n, "--name")) return 2;
|
|
9423
9835
|
const body = args.flags.body ? String(args.flags.body) : args.flags["body-file"] ? node_fs.readFileSync(String(args.flags["body-file"]), "utf8") : "";
|
|
9424
9836
|
const spec = {
|
|
@@ -9646,12 +10058,13 @@ function wrap(text, indent = 4) {
|
|
|
9646
10058
|
//#region packages/knowledge/skills/src/cmds.ts
|
|
9647
10059
|
/** kb group — long-term knowledge: store, retrieve, connect, promote to skill.
|
|
9648
10060
|
* Writes (add/ingest) run on the TypeORM write service (src/writes.ts). */
|
|
9649
|
-
var cmds_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$
|
|
10061
|
+
var cmds_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$2 });
|
|
9650
10062
|
init_fmt();
|
|
9651
10063
|
require_text.init_text();
|
|
10064
|
+
init_eventbus();
|
|
9652
10065
|
init_orm();
|
|
9653
10066
|
init_writes();
|
|
9654
|
-
function run$
|
|
10067
|
+
function run$2(env, action, rest, args) {
|
|
9655
10068
|
const { ctx, store } = env;
|
|
9656
10069
|
const readBody = () => {
|
|
9657
10070
|
if (args.flags.body) return String(args.flags.body);
|
|
@@ -9784,493 +10197,215 @@ function run$3(env, action, rest, args) {
|
|
|
9784
10197
|
"conf",
|
|
9785
10198
|
"reuse",
|
|
9786
10199
|
"updated"
|
|
9787
|
-
], [
|
|
9788
|
-
"id",
|
|
9789
|
-
"title",
|
|
9790
|
-
"tags",
|
|
9791
|
-
"confidence",
|
|
9792
|
-
"reuse",
|
|
9793
|
-
"updated"
|
|
9794
|
-
], "kb add --title ... --body ... | kb import <dir>");
|
|
9795
|
-
return 0;
|
|
9796
|
-
case "rm":
|
|
9797
|
-
case "remove": {
|
|
9798
|
-
const id = rest[0];
|
|
9799
|
-
if (!id) {
|
|
9800
|
-
bad(ctx, "usage: kb rm <id>");
|
|
9801
|
-
return 2;
|
|
9802
|
-
}
|
|
9803
|
-
const note = store.kbGet(id);
|
|
9804
|
-
if (!note) {
|
|
9805
|
-
bad(ctx, `note not found: ${id}`);
|
|
9806
|
-
return 1;
|
|
9807
|
-
}
|
|
9808
|
-
store.db.run("DELETE FROM kb_fts WHERE id=?", [id]);
|
|
9809
|
-
store.db.run("DELETE FROM kb_notes WHERE id=?", [id]);
|
|
9810
|
-
ok(ctx, `note removed: ${id} ${String(note.title || "").slice(0, 40)}`);
|
|
9811
|
-
return 0;
|
|
9812
|
-
}
|
|
9813
|
-
case "show": {
|
|
9814
|
-
const n = store.kbGet(rest[0]);
|
|
9815
|
-
if (!n) {
|
|
9816
|
-
bad(ctx, `note not found: ${rest[0]}`);
|
|
9817
|
-
return 1;
|
|
9818
|
-
}
|
|
9819
|
-
if (ctx.json) {
|
|
9820
|
-
console.log(JSON.stringify(n, null, 2));
|
|
9821
|
-
return 0;
|
|
9822
|
-
}
|
|
9823
|
-
head(ctx, n.title);
|
|
9824
|
-
show(ctx, {
|
|
9825
|
-
id: n.id,
|
|
9826
|
-
tags: n.tags || "-",
|
|
9827
|
-
confidence: n.confidence,
|
|
9828
|
-
reuse: n.reuse,
|
|
9829
|
-
origin: n.origin || "-"
|
|
9830
|
-
});
|
|
9831
|
-
out(ctx, "");
|
|
9832
|
-
out(ctx, n.body);
|
|
9833
|
-
const links = store.kbLinks(n.id);
|
|
9834
|
-
if (links.length) {
|
|
9835
|
-
out(ctx, paint(C.bold, "\n links", ctx.color));
|
|
9836
|
-
for (const l of links) out(ctx, ` - ${l.rel}: ${l.other}`);
|
|
9837
|
-
}
|
|
9838
|
-
return 0;
|
|
9839
|
-
}
|
|
9840
|
-
case "link": {
|
|
9841
|
-
const a = store.kbGet(rest[0]);
|
|
9842
|
-
const b = store.kbGet(rest[1]);
|
|
9843
|
-
if (!a || !b) {
|
|
9844
|
-
bad(ctx, "both notes must exist");
|
|
9845
|
-
return 1;
|
|
9846
|
-
}
|
|
9847
|
-
store.kbLink(a.id, b.id, String(args.flags.rel || "related"));
|
|
9848
|
-
ok(ctx, `linked ${a.title} --${args.flags.rel || "related"}--> ${b.title}`);
|
|
9849
|
-
return 0;
|
|
9850
|
-
}
|
|
9851
|
-
case "ingest": {
|
|
9852
|
-
if (!rest[0]) {
|
|
9853
|
-
bad(ctx, "usage: kb ingest <artifactId> [--force]");
|
|
9854
|
-
return 2;
|
|
9855
|
-
}
|
|
9856
|
-
const forceKb = args.flags.force === true || args.flags.force === "true";
|
|
9857
|
-
return getOrm(env.db.path).then((ds) => kbIngestArtifact(ds, rest[0], forceKb)).then((r) => {
|
|
9858
|
-
writeResult(ctx, r, (row) => `artifact ingested into kb: ${row.id} ${require_text.clip(row.title, 50)}`);
|
|
9859
|
-
return 0;
|
|
9860
|
-
}).catch((e) => {
|
|
9861
|
-
bad(ctx, e?.message || String(e));
|
|
9862
|
-
return 1;
|
|
9863
|
-
});
|
|
9864
|
-
}
|
|
9865
|
-
case "promote": {
|
|
9866
|
-
if (!rest[0]) {
|
|
9867
|
-
bad(ctx, "usage: kb promote <noteId> [--name <skill>]");
|
|
9868
|
-
return 2;
|
|
9869
|
-
}
|
|
9870
|
-
const note = store.kbGet(rest[0]);
|
|
9871
|
-
if (!note) {
|
|
9872
|
-
bad(ctx, `kb note not found: ${rest[0]}`);
|
|
9873
|
-
return 1;
|
|
9874
|
-
}
|
|
9875
|
-
const name = args.flags.name ? String(args.flags.name) : note.title;
|
|
9876
|
-
return getOrm(env.db.path).then((ds) => skillUpsert(ds, {
|
|
9877
|
-
name,
|
|
9878
|
-
description: deriveL1(note.body || "", note.title),
|
|
9879
|
-
body: note.body,
|
|
9880
|
-
tags: note.tags,
|
|
9881
|
-
origin: `kb:${note.id}`
|
|
9882
|
-
})).then((s) => {
|
|
9883
|
-
ok(ctx, `promoted to skill: ${s.name}`);
|
|
9884
|
-
out(ctx, paint(C.dim, ` L1: ${require_text.clip(String(s.description), 100)}`, ctx.color));
|
|
9885
|
-
return 0;
|
|
9886
|
-
}).catch((e) => {
|
|
9887
|
-
bad(ctx, e?.message || String(e));
|
|
9888
|
-
return 1;
|
|
9889
|
-
});
|
|
9890
|
-
}
|
|
9891
|
-
case "import": {
|
|
9892
|
-
const target = rest[0];
|
|
9893
|
-
if (!target) {
|
|
9894
|
-
bad(ctx, "usage: kb import <dir|file.md> [--tags ..]");
|
|
9895
|
-
return 2;
|
|
9896
|
-
}
|
|
9897
|
-
let st;
|
|
9898
|
-
try {
|
|
9899
|
-
st = node_fs.statSync(target);
|
|
9900
|
-
} catch {
|
|
9901
|
-
bad(ctx, `not found: ${target}`);
|
|
9902
|
-
return 1;
|
|
9903
|
-
}
|
|
9904
|
-
const files = [];
|
|
9905
|
-
if (st.isFile()) files.push(target);
|
|
9906
|
-
else for (const f of node_fs.readdirSync(target, { recursive: true })) if (String(f).toLowerCase().endsWith(".md")) files.push(node_path.join(target, String(f)));
|
|
9907
|
-
if (!files.length) {
|
|
9908
|
-
bad(ctx, `no .md files under ${target}`);
|
|
9909
|
-
return 1;
|
|
9910
|
-
}
|
|
9911
|
-
files.sort();
|
|
9912
|
-
const MAX_FILE_BYTES = 1048576;
|
|
9913
|
-
const skipped = [];
|
|
9914
|
-
const importable = files.filter((f) => {
|
|
9915
|
-
const sz = node_fs.statSync(f).size;
|
|
9916
|
-
if (sz > MAX_FILE_BYTES && !args.flags.force) {
|
|
9917
|
-
skipped.push({
|
|
9918
|
-
file: f,
|
|
9919
|
-
size: `${(sz / 1024 / 1024).toFixed(1)}MB`
|
|
9920
|
-
});
|
|
9921
|
-
return false;
|
|
9922
|
-
}
|
|
9923
|
-
return true;
|
|
9924
|
-
});
|
|
9925
|
-
if (skipped.length && !ctx.json) {
|
|
9926
|
-
out(ctx, paint(C.yellow, ` ! skipped ${skipped.length} file(s) >1MB (--force to include):`, ctx.color));
|
|
9927
|
-
for (const sk of skipped) out(ctx, paint(C.dim, ` ${sk.file} (${sk.size})`, ctx.color));
|
|
9928
|
-
}
|
|
9929
|
-
if (!importable.length) {
|
|
9930
|
-
bad(ctx, `all ${files.length} file(s) exceeded 1MB limit — use --force to import anyway`);
|
|
9931
|
-
return 1;
|
|
9932
|
-
}
|
|
9933
|
-
const items = importable.map((f) => {
|
|
9934
|
-
const raw = node_fs.readFileSync(f, "utf8");
|
|
9935
|
-
const { meta, body } = require_text.parseFrontmatter(raw);
|
|
9936
|
-
const heading = /^#\s+(.+)\s*$/m.exec(body)?.[1]?.trim();
|
|
9937
|
-
return {
|
|
9938
|
-
title: meta.title || meta.name || heading || node_path.basename(f, ".md"),
|
|
9939
|
-
body,
|
|
9940
|
-
tags: meta.tags || String(args.flags.tags || ""),
|
|
9941
|
-
source_url: meta.url || "",
|
|
9942
|
-
confidence: meta.confidence || "B",
|
|
9943
|
-
origin: `import:${f}`
|
|
9944
|
-
};
|
|
9945
|
-
});
|
|
9946
|
-
return getOrm(env.db.path).then((ds) => kbImportBulk(ds, items)).then((r) => {
|
|
9947
|
-
if (ctx.json) console.log(JSON.stringify({
|
|
9948
|
-
added: r.added,
|
|
9949
|
-
duplicates: r.dups,
|
|
9950
|
-
files: files.length,
|
|
9951
|
-
target
|
|
9952
|
-
}, null, 2));
|
|
9953
|
-
else ok(ctx, `kb import: ${r.added} added, ${r.dups} duplicate(s) kept — ${files.length} file(s) from ${target}`);
|
|
9954
|
-
return 0;
|
|
9955
|
-
}).catch((e) => {
|
|
9956
|
-
bad(ctx, e?.message || String(e));
|
|
9957
|
-
return 1;
|
|
9958
|
-
});
|
|
9959
|
-
}
|
|
9960
|
-
case "":
|
|
9961
|
-
out(ctx, paint(C.dim, " kb: add | import <dir|file> | search | list | show | link | ingest <artifactId> | promote <noteId>", ctx.color));
|
|
9962
|
-
return 0;
|
|
9963
|
-
default:
|
|
9964
|
-
bad(ctx, `unknown kb action: ${action}`);
|
|
9965
|
-
return 2;
|
|
9966
|
-
}
|
|
9967
|
-
}
|
|
9968
|
-
//#endregion
|
|
9969
|
-
//#region packages/ops/tools/src/rels.ts
|
|
9970
|
-
var rels_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
9971
|
-
REL_TYPES: () => REL_TYPES,
|
|
9972
|
-
RelError: () => RelError,
|
|
9973
|
-
relAdd: () => relAdd,
|
|
9974
|
-
relRm: () => relRm,
|
|
9975
|
-
resolveId: () => resolveId,
|
|
9976
|
-
resolveRef: () => resolveRef,
|
|
9977
|
-
run: () => run$2
|
|
9978
|
-
});
|
|
9979
|
-
init_fmt();
|
|
9980
|
-
require_text.init_text();
|
|
9981
|
-
init_orm();
|
|
9982
|
-
const REL_TYPES = [
|
|
9983
|
-
"node",
|
|
9984
|
-
"claim",
|
|
9985
|
-
"evidence",
|
|
9986
|
-
"source",
|
|
9987
|
-
"query",
|
|
9988
|
-
"kb",
|
|
9989
|
-
"artifact",
|
|
9990
|
-
"skill"
|
|
9991
|
-
];
|
|
9992
|
-
/** Resolve one `<type>:<ref>` against the database → { type, id, display }.
|
|
9993
|
-
* Throws RelError(code) on unknown type / missing row. */
|
|
9994
|
-
var RelError = class extends Error {
|
|
9995
|
-
code;
|
|
9996
|
-
constructor(code, message) {
|
|
9997
|
-
super(message);
|
|
9998
|
-
this.code = code;
|
|
9999
|
-
}
|
|
10000
|
-
};
|
|
10001
|
-
const DISPLAY = {
|
|
10002
|
-
node: (db, ref) => {
|
|
10003
|
-
const [treeName, key] = ref.includes("/") ? [ref.slice(0, ref.indexOf("/")), ref.slice(ref.indexOf("/") + 1)] : [null, ref];
|
|
10004
|
-
const cur = treeName || db.metaGet("current_tree") || "";
|
|
10005
|
-
const rows = db.all("SELECT n.id, n.key, n.name FROM nodes n JOIN trees t ON t.id=n.tree_id WHERE (t.name=? OR t.id=?) AND (n.key=? OR n.id=?)", [
|
|
10006
|
-
cur,
|
|
10007
|
-
cur,
|
|
10008
|
-
key,
|
|
10009
|
-
key
|
|
10010
|
-
]);
|
|
10011
|
-
if (!rows.length) throw new RelError(1, `node not found: ${ref}`);
|
|
10012
|
-
return {
|
|
10013
|
-
id: String(rows[0].id),
|
|
10014
|
-
display: String(rows[0].name || rows[0].key)
|
|
10015
|
-
};
|
|
10016
|
-
},
|
|
10017
|
-
claim: (db, ref) => {
|
|
10018
|
-
const r = db.get("SELECT id, text FROM claims WHERE id=? LIMIT 1", [ref]);
|
|
10019
|
-
if (!r) throw new RelError(1, `claim not found: ${ref}`);
|
|
10020
|
-
return {
|
|
10021
|
-
id: String(r.id),
|
|
10022
|
-
display: String(r.text).slice(0, 60)
|
|
10023
|
-
};
|
|
10024
|
-
},
|
|
10025
|
-
evidence: (db, ref) => {
|
|
10026
|
-
const r = db.get("SELECT id, quote FROM evidence WHERE id=? LIMIT 1", [ref]);
|
|
10027
|
-
if (!r) throw new RelError(1, `evidence not found: ${ref}`);
|
|
10028
|
-
return {
|
|
10029
|
-
id: String(r.id),
|
|
10030
|
-
display: String(r.quote).slice(0, 60)
|
|
10031
|
-
};
|
|
10032
|
-
},
|
|
10033
|
-
source: (db, ref) => {
|
|
10034
|
-
const r = db.get("SELECT id, title FROM sources WHERE id=? LIMIT 1", [ref]);
|
|
10035
|
-
if (!r) throw new RelError(1, `source not found: ${ref}`);
|
|
10036
|
-
return {
|
|
10037
|
-
id: String(r.id),
|
|
10038
|
-
display: String(r.title)
|
|
10039
|
-
};
|
|
10040
|
-
},
|
|
10041
|
-
query: (db, ref) => {
|
|
10042
|
-
const r = db.get("SELECT id, text FROM queries WHERE id=? LIMIT 1", [ref]);
|
|
10043
|
-
if (!r) throw new RelError(1, `query not found: ${ref}`);
|
|
10044
|
-
return {
|
|
10045
|
-
id: String(r.id),
|
|
10046
|
-
display: String(r.text).slice(0, 60)
|
|
10047
|
-
};
|
|
10048
|
-
},
|
|
10049
|
-
kb: (db, ref) => {
|
|
10050
|
-
const r = db.get("SELECT id, title FROM kb_notes WHERE id=? OR title=? LIMIT 1", [ref, ref]);
|
|
10051
|
-
if (!r) throw new RelError(1, `kb note not found: ${ref}`);
|
|
10052
|
-
return {
|
|
10053
|
-
id: String(r.id),
|
|
10054
|
-
display: String(r.title)
|
|
10055
|
-
};
|
|
10056
|
-
},
|
|
10057
|
-
artifact: (db, ref) => {
|
|
10058
|
-
const r = db.get("SELECT id, title FROM artifacts WHERE id=? OR title=? LIMIT 1", [ref, ref]);
|
|
10059
|
-
if (!r) throw new RelError(1, `artifact not found: ${ref}`);
|
|
10060
|
-
return {
|
|
10061
|
-
id: String(r.id),
|
|
10062
|
-
display: String(r.title)
|
|
10063
|
-
};
|
|
10064
|
-
},
|
|
10065
|
-
skill: (db, ref) => {
|
|
10066
|
-
const r = db.get("SELECT name, description FROM skills WHERE name=? LIMIT 1", [ref]);
|
|
10067
|
-
if (!r) throw new RelError(1, `skill not found: ${ref}`);
|
|
10068
|
-
return {
|
|
10069
|
-
id: String(r.name),
|
|
10070
|
-
display: String(r.name)
|
|
10071
|
-
};
|
|
10072
|
-
}
|
|
10073
|
-
};
|
|
10074
|
-
function resolveRef(env, ref) {
|
|
10075
|
-
const i = ref.indexOf(":");
|
|
10076
|
-
if (i < 1) throw new RelError(2, `bad reference '${ref}' — want <type>:<ref> where type ∈ ${REL_TYPES.join("|")}`);
|
|
10077
|
-
const type = ref.slice(0, i);
|
|
10078
|
-
const ident = ref.slice(i + 1);
|
|
10079
|
-
const fn = DISPLAY[type];
|
|
10080
|
-
if (!fn) throw new RelError(2, `unknown reference type '${type}' — want ${REL_TYPES.join("|")}`);
|
|
10081
|
-
const r = fn(env.db, ident);
|
|
10082
|
-
return {
|
|
10083
|
-
type,
|
|
10084
|
-
id: r.id,
|
|
10085
|
-
display: r.display
|
|
10086
|
-
};
|
|
10087
|
-
}
|
|
10088
|
-
/** Reverse lookup: type+id → display (for the webui related section). */
|
|
10089
|
-
function resolveId(env, type, id) {
|
|
10090
|
-
const fn = DISPLAY[type];
|
|
10091
|
-
if (!fn) return id;
|
|
10092
|
-
try {
|
|
10093
|
-
return fn(env.db, id).display;
|
|
10094
|
-
} catch {
|
|
10095
|
-
return id;
|
|
10096
|
-
}
|
|
10097
|
-
}
|
|
10098
|
-
async function relAdd(env, srcRef, dstRef, rel = "related") {
|
|
10099
|
-
const src = resolveRef(env, srcRef);
|
|
10100
|
-
const dst = resolveRef(env, dstRef);
|
|
10101
|
-
return (await getOrm(env.db.path)).transaction(async (mgr) => {
|
|
10102
|
-
const ex = await mgr.query(`SELECT id FROM relations WHERE src_type=? AND src_id=? AND dst_type=? AND dst_id=? AND rel=?`, [
|
|
10103
|
-
src.type,
|
|
10104
|
-
src.id,
|
|
10105
|
-
dst.type,
|
|
10106
|
-
dst.id,
|
|
10107
|
-
rel
|
|
10108
|
-
]);
|
|
10109
|
-
if (ex.length) return {
|
|
10110
|
-
row: (await mgr.query(`SELECT * FROM relations WHERE id=?`, [ex[0].id]))[0],
|
|
10111
|
-
duplicate: true
|
|
10112
|
-
};
|
|
10113
|
-
const id = require_text.shortId([
|
|
10114
|
-
"rel",
|
|
10115
|
-
src.id,
|
|
10116
|
-
dst.id,
|
|
10117
|
-
rel
|
|
10118
|
-
], 10);
|
|
10119
|
-
await mgr.query(`INSERT INTO relations(id,src_type,src_id,dst_type,dst_id,rel,weight,created) VALUES(?,?,?,?,?,?,1,?)`, [
|
|
10120
|
-
id,
|
|
10121
|
-
src.type,
|
|
10122
|
-
src.id,
|
|
10123
|
-
dst.type,
|
|
10124
|
-
dst.id,
|
|
10125
|
-
rel,
|
|
10126
|
-
require_text.nowIso()
|
|
10127
|
-
]);
|
|
10128
|
-
await mgr.query(`INSERT INTO events(id,ts,actor,kind,entity,entity_id,payload) VALUES(?,?,?,?,?,?,?)`, [
|
|
10129
|
-
require_text.shortId([
|
|
10130
|
-
"ev",
|
|
10131
|
-
"rel.add",
|
|
10132
|
-
id
|
|
10133
|
-
], 12),
|
|
10134
|
-
require_text.nowIso(),
|
|
10135
|
-
"cli",
|
|
10136
|
-
"rel.add",
|
|
10137
|
-
"relation",
|
|
10138
|
-
id,
|
|
10139
|
-
JSON.stringify({
|
|
10140
|
-
src: `${src.type}:${src.id}`,
|
|
10141
|
-
dst: `${dst.type}:${dst.id}`,
|
|
10142
|
-
rel
|
|
10143
|
-
})
|
|
10144
|
-
]);
|
|
10145
|
-
return {
|
|
10146
|
-
row: (await mgr.query(`SELECT * FROM relations WHERE id=?`, [id]))[0],
|
|
10147
|
-
duplicate: false
|
|
10148
|
-
};
|
|
10149
|
-
});
|
|
10150
|
-
}
|
|
10151
|
-
async function relRm(env, srcRef, dstRef, rel = "related") {
|
|
10152
|
-
const src = resolveRef(env, srcRef);
|
|
10153
|
-
const dst = resolveRef(env, dstRef);
|
|
10154
|
-
await (await getOrm(env.db.path)).transaction(async (mgr) => {
|
|
10155
|
-
if (!(await mgr.query(`SELECT id FROM relations WHERE src_type=? AND src_id=? AND dst_type=? AND dst_id=? AND rel=?`, [
|
|
10156
|
-
src.type,
|
|
10157
|
-
src.id,
|
|
10158
|
-
dst.type,
|
|
10159
|
-
dst.id,
|
|
10160
|
-
rel
|
|
10161
|
-
])).length) throw new RelError(1, `relation not found: ${srcRef} → ${dstRef} (${rel})`);
|
|
10162
|
-
await mgr.query(`DELETE FROM relations WHERE src_type=? AND src_id=? AND dst_type=? AND dst_id=? AND rel=?`, [
|
|
10163
|
-
src.type,
|
|
10164
|
-
src.id,
|
|
10165
|
-
dst.type,
|
|
10166
|
-
dst.id,
|
|
10167
|
-
rel
|
|
10168
|
-
]);
|
|
10169
|
-
});
|
|
10170
|
-
}
|
|
10171
|
-
function run$2(env, action, rest, args) {
|
|
10172
|
-
const { ctx } = env;
|
|
10173
|
-
if (action === "add") {
|
|
10174
|
-
const [a, b] = rest;
|
|
10175
|
-
if (!a || !b) {
|
|
10176
|
-
bad(ctx, "usage: rel add <type>:<ref> <type>:<ref> [--rel <type>]");
|
|
10177
|
-
return 2;
|
|
10178
|
-
}
|
|
10179
|
-
const rel = String(args.flags.rel || "related");
|
|
10180
|
-
return (async () => {
|
|
10200
|
+
], [
|
|
10201
|
+
"id",
|
|
10202
|
+
"title",
|
|
10203
|
+
"tags",
|
|
10204
|
+
"confidence",
|
|
10205
|
+
"reuse",
|
|
10206
|
+
"updated"
|
|
10207
|
+
], "kb add --title ... --body ... | kb import <dir>");
|
|
10208
|
+
return 0;
|
|
10209
|
+
case "rm":
|
|
10210
|
+
case "remove": {
|
|
10211
|
+
const id = rest[0];
|
|
10212
|
+
if (!id) {
|
|
10213
|
+
bad(ctx, "usage: kb rm <id>");
|
|
10214
|
+
return 2;
|
|
10215
|
+
}
|
|
10216
|
+
const note = store.kbGet(id);
|
|
10217
|
+
if (!note) {
|
|
10218
|
+
bad(ctx, `note not found: ${id}`);
|
|
10219
|
+
return 1;
|
|
10220
|
+
}
|
|
10221
|
+
store.db.run("DELETE FROM kb_fts WHERE id=?", [id]);
|
|
10222
|
+
store.db.run("DELETE FROM kb_notes WHERE id=?", [id]);
|
|
10181
10223
|
try {
|
|
10182
|
-
const
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10224
|
+
const ev = {
|
|
10225
|
+
id: `ev${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
|
|
10226
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10227
|
+
actor: process.env.YGL_SESSION || "cli",
|
|
10228
|
+
kind: "kb.rm",
|
|
10229
|
+
entity: "kb_note",
|
|
10230
|
+
entity_id: id,
|
|
10231
|
+
payload: JSON.stringify({ title: String(note.title || "") }),
|
|
10232
|
+
level: "info"
|
|
10233
|
+
};
|
|
10234
|
+
store.db.run("INSERT INTO events(id,ts,actor,kind,entity,entity_id,payload,level) VALUES(?,?,?,?,?,?,?,?)", [
|
|
10235
|
+
ev.id,
|
|
10236
|
+
ev.ts,
|
|
10237
|
+
ev.actor,
|
|
10238
|
+
ev.kind,
|
|
10239
|
+
ev.entity,
|
|
10240
|
+
ev.entity_id,
|
|
10241
|
+
ev.payload,
|
|
10242
|
+
ev.level
|
|
10243
|
+
]);
|
|
10244
|
+
emitEvent({
|
|
10245
|
+
kind: "kb.rm",
|
|
10246
|
+
entity: "kb_note",
|
|
10247
|
+
entityId: id,
|
|
10248
|
+
payload: JSON.stringify({ title: String(note.title || "") }),
|
|
10249
|
+
ts: ev.ts
|
|
10250
|
+
});
|
|
10251
|
+
} catch {}
|
|
10252
|
+
ok(ctx, `note removed: ${id} ${String(note.title || "").slice(0, 40)}`);
|
|
10253
|
+
return 0;
|
|
10254
|
+
}
|
|
10255
|
+
case "show": {
|
|
10256
|
+
const n = store.kbGet(rest[0]);
|
|
10257
|
+
if (!n) {
|
|
10258
|
+
bad(ctx, `note not found: ${rest[0]}`);
|
|
10259
|
+
return 1;
|
|
10260
|
+
}
|
|
10261
|
+
if (ctx.json) {
|
|
10262
|
+
console.log(JSON.stringify(n, null, 2));
|
|
10195
10263
|
return 0;
|
|
10196
|
-
} catch (e) {
|
|
10197
|
-
if (e instanceof RelError) {
|
|
10198
|
-
bad(ctx, e.message);
|
|
10199
|
-
return e.code;
|
|
10200
|
-
}
|
|
10201
|
-
throw e;
|
|
10202
10264
|
}
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10265
|
+
head(ctx, n.title);
|
|
10266
|
+
show(ctx, {
|
|
10267
|
+
id: n.id,
|
|
10268
|
+
tags: n.tags || "-",
|
|
10269
|
+
confidence: n.confidence,
|
|
10270
|
+
reuse: n.reuse,
|
|
10271
|
+
origin: n.origin || "-"
|
|
10272
|
+
});
|
|
10273
|
+
out(ctx, "");
|
|
10274
|
+
out(ctx, n.body);
|
|
10275
|
+
const links = store.kbLinks(n.id);
|
|
10276
|
+
if (links.length) {
|
|
10277
|
+
out(ctx, paint(C.bold, "\n links", ctx.color));
|
|
10278
|
+
for (const l of links) out(ctx, ` - ${l.rel}: ${l.other}`);
|
|
10279
|
+
}
|
|
10280
|
+
return 0;
|
|
10210
10281
|
}
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10282
|
+
case "link": {
|
|
10283
|
+
const a = store.kbGet(rest[0]);
|
|
10284
|
+
const b = store.kbGet(rest[1]);
|
|
10285
|
+
if (!a || !b) {
|
|
10286
|
+
bad(ctx, "both notes must exist");
|
|
10287
|
+
return 1;
|
|
10288
|
+
}
|
|
10289
|
+
store.kbLink(a.id, b.id, String(args.flags.rel || "related"));
|
|
10290
|
+
ok(ctx, `linked ${a.title} --${args.flags.rel || "related"}--> ${b.title}`);
|
|
10291
|
+
return 0;
|
|
10292
|
+
}
|
|
10293
|
+
case "ingest": {
|
|
10294
|
+
if (!rest[0]) {
|
|
10295
|
+
bad(ctx, "usage: kb ingest <artifactId> [--force]");
|
|
10296
|
+
return 2;
|
|
10297
|
+
}
|
|
10298
|
+
const forceKb = args.flags.force === true || args.flags.force === "true";
|
|
10299
|
+
return getOrm(env.db.path).then((ds) => kbIngestArtifact(ds, rest[0], forceKb)).then((r) => {
|
|
10300
|
+
writeResult(ctx, r, (row) => `artifact ingested into kb: ${row.id} ${require_text.clip(row.title, 50)}`);
|
|
10217
10301
|
return 0;
|
|
10218
|
-
}
|
|
10219
|
-
|
|
10220
|
-
|
|
10221
|
-
|
|
10222
|
-
|
|
10223
|
-
|
|
10302
|
+
}).catch((e) => {
|
|
10303
|
+
bad(ctx, e?.message || String(e));
|
|
10304
|
+
return 1;
|
|
10305
|
+
});
|
|
10306
|
+
}
|
|
10307
|
+
case "promote": {
|
|
10308
|
+
if (!rest[0]) {
|
|
10309
|
+
bad(ctx, "usage: kb promote <noteId> [--name <skill>]");
|
|
10310
|
+
return 2;
|
|
10224
10311
|
}
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
const params = [];
|
|
10230
|
-
let where = "1=1";
|
|
10231
|
-
const srcF = String(args.flags.src || "");
|
|
10232
|
-
const dstF = String(args.flags.dst || "");
|
|
10233
|
-
const relF = String(args.flags.rel || "");
|
|
10234
|
-
if (srcF) {
|
|
10235
|
-
const s = resolveRef(env, srcF);
|
|
10236
|
-
where += " AND src_type=? AND src_id=?";
|
|
10237
|
-
params.push(s.type, s.id);
|
|
10312
|
+
const note = store.kbGet(rest[0]);
|
|
10313
|
+
if (!note) {
|
|
10314
|
+
bad(ctx, `kb note not found: ${rest[0]}`);
|
|
10315
|
+
return 1;
|
|
10238
10316
|
}
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10317
|
+
const name = args.flags.name ? String(args.flags.name) : note.title;
|
|
10318
|
+
return getOrm(env.db.path).then((ds) => skillUpsert(ds, {
|
|
10319
|
+
name,
|
|
10320
|
+
description: deriveL1(note.body || "", note.title),
|
|
10321
|
+
body: note.body,
|
|
10322
|
+
tags: note.tags,
|
|
10323
|
+
origin: `kb:${note.id}`
|
|
10324
|
+
})).then((s) => {
|
|
10325
|
+
ok(ctx, `promoted to skill: ${s.name}`);
|
|
10326
|
+
out(ctx, paint(C.dim, ` L1: ${require_text.clip(String(s.description), 100)}`, ctx.color));
|
|
10327
|
+
return 0;
|
|
10328
|
+
}).catch((e) => {
|
|
10329
|
+
bad(ctx, e?.message || String(e));
|
|
10330
|
+
return 1;
|
|
10331
|
+
});
|
|
10332
|
+
}
|
|
10333
|
+
case "import": {
|
|
10334
|
+
const target = rest[0];
|
|
10335
|
+
if (!target) {
|
|
10336
|
+
bad(ctx, "usage: kb import <dir|file.md> [--tags ..]");
|
|
10337
|
+
return 2;
|
|
10243
10338
|
}
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10339
|
+
let st;
|
|
10340
|
+
try {
|
|
10341
|
+
st = node_fs.statSync(target);
|
|
10342
|
+
} catch {
|
|
10343
|
+
bad(ctx, `not found: ${target}`);
|
|
10344
|
+
return 1;
|
|
10247
10345
|
}
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10346
|
+
const files = [];
|
|
10347
|
+
if (st.isFile()) files.push(target);
|
|
10348
|
+
else for (const f of node_fs.readdirSync(target, { recursive: true })) if (String(f).toLowerCase().endsWith(".md")) files.push(node_path.join(target, String(f)));
|
|
10349
|
+
if (!files.length) {
|
|
10350
|
+
bad(ctx, `no .md files under ${target}`);
|
|
10351
|
+
return 1;
|
|
10352
|
+
}
|
|
10353
|
+
files.sort();
|
|
10354
|
+
const MAX_FILE_BYTES = 1048576;
|
|
10355
|
+
const skipped = [];
|
|
10356
|
+
const importable = files.filter((f) => {
|
|
10357
|
+
const sz = node_fs.statSync(f).size;
|
|
10358
|
+
if (sz > MAX_FILE_BYTES && !args.flags.force) {
|
|
10359
|
+
skipped.push({
|
|
10360
|
+
file: f,
|
|
10361
|
+
size: `${(sz / 1024 / 1024).toFixed(1)}MB`
|
|
10362
|
+
});
|
|
10363
|
+
return false;
|
|
10364
|
+
}
|
|
10365
|
+
return true;
|
|
10366
|
+
});
|
|
10367
|
+
if (skipped.length && !ctx.json) {
|
|
10368
|
+
out(ctx, paint(C.yellow, ` ! skipped ${skipped.length} file(s) >1MB (--force to include):`, ctx.color));
|
|
10369
|
+
for (const sk of skipped) out(ctx, paint(C.dim, ` ${sk.file} (${sk.size})`, ctx.color));
|
|
10370
|
+
}
|
|
10371
|
+
if (!importable.length) {
|
|
10372
|
+
bad(ctx, `all ${files.length} file(s) exceeded 1MB limit — use --force to import anyway`);
|
|
10373
|
+
return 1;
|
|
10374
|
+
}
|
|
10375
|
+
const items = importable.map((f) => {
|
|
10376
|
+
const raw = node_fs.readFileSync(f, "utf8");
|
|
10377
|
+
const { meta, body } = require_text.parseFrontmatter(raw);
|
|
10378
|
+
const heading = /^#\s+(.+)\s*$/m.exec(body)?.[1]?.trim();
|
|
10379
|
+
return {
|
|
10380
|
+
title: meta.title || meta.name || heading || node_path.basename(f, ".md"),
|
|
10381
|
+
body,
|
|
10382
|
+
tags: meta.tags || String(args.flags.tags || ""),
|
|
10383
|
+
source_url: meta.url || "",
|
|
10384
|
+
confidence: meta.confidence || "B",
|
|
10385
|
+
origin: `import:${f}`
|
|
10386
|
+
};
|
|
10387
|
+
});
|
|
10388
|
+
return getOrm(env.db.path).then((ds) => kbImportBulk(ds, items)).then((r) => {
|
|
10389
|
+
if (ctx.json) console.log(JSON.stringify({
|
|
10390
|
+
added: r.added,
|
|
10391
|
+
duplicates: r.dups,
|
|
10392
|
+
files: files.length,
|
|
10393
|
+
target
|
|
10394
|
+
}, null, 2));
|
|
10395
|
+
else ok(ctx, `kb import: ${r.added} added, ${r.dups} duplicate(s) kept — ${files.length} file(s) from ${target}`);
|
|
10396
|
+
return 0;
|
|
10397
|
+
}).catch((e) => {
|
|
10398
|
+
bad(ctx, e?.message || String(e));
|
|
10399
|
+
return 1;
|
|
10400
|
+
});
|
|
10259
10401
|
}
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
"src_display",
|
|
10267
|
-
"rel",
|
|
10268
|
-
"dst_display"
|
|
10269
|
-
]);
|
|
10270
|
-
return 0;
|
|
10402
|
+
case "":
|
|
10403
|
+
out(ctx, paint(C.dim, " kb: add | import <dir|file> | search | list | show | link | ingest <artifactId> | promote <noteId>", ctx.color));
|
|
10404
|
+
return 0;
|
|
10405
|
+
default:
|
|
10406
|
+
bad(ctx, `unknown kb action: ${action}`);
|
|
10407
|
+
return 2;
|
|
10271
10408
|
}
|
|
10272
|
-
bad(ctx, "usage: rel [add|rm|list] ... (see ygl-trees help)");
|
|
10273
|
-
return 2;
|
|
10274
10409
|
}
|
|
10275
10410
|
//#endregion
|
|
10276
10411
|
//#region packages/ops/tools/src/webui.ts
|
|
@@ -11459,17 +11594,20 @@ function run(env, action, rest, args) {
|
|
|
11459
11594
|
{
|
|
11460
11595
|
kind: "kb.add",
|
|
11461
11596
|
table: "kb_notes",
|
|
11462
|
-
label: "kb"
|
|
11597
|
+
label: "kb",
|
|
11598
|
+
rm: "kb.rm"
|
|
11463
11599
|
},
|
|
11464
11600
|
{
|
|
11465
11601
|
kind: "source.add",
|
|
11466
11602
|
table: "sources",
|
|
11467
|
-
label: "source"
|
|
11603
|
+
label: "source",
|
|
11604
|
+
rm: "source.rm"
|
|
11468
11605
|
},
|
|
11469
11606
|
{
|
|
11470
11607
|
kind: "evidence.add",
|
|
11471
11608
|
table: "evidence",
|
|
11472
|
-
label: "evidence"
|
|
11609
|
+
label: "evidence",
|
|
11610
|
+
rm: "evidence.rm"
|
|
11473
11611
|
}
|
|
11474
11612
|
];
|
|
11475
11613
|
const phantoms = [];
|
|
@@ -11477,13 +11615,14 @@ function run(env, action, rest, args) {
|
|
|
11477
11615
|
const rows = db.all(`SELECT e.entity_id id, e.ts FROM events e
|
|
11478
11616
|
WHERE e.kind='${spec.kind}'
|
|
11479
11617
|
AND e.ts >= datetime('now', '-2 days')
|
|
11480
|
-
AND NOT EXISTS (SELECT 1 FROM ${spec.table} t WHERE t.id = e.entity_id)
|
|
11618
|
+
AND NOT EXISTS (SELECT 1 FROM ${spec.table} t WHERE t.id = e.entity_id)
|
|
11619
|
+
AND NOT EXISTS (SELECT 1 FROM events r WHERE r.kind='${spec.rm}' AND r.entity_id = e.entity_id)`);
|
|
11481
11620
|
for (const r of rows) phantoms.push(`${spec.label} ${r.id} (${String(r.ts).slice(0, 16)})`);
|
|
11482
11621
|
}
|
|
11483
11622
|
checks.push({
|
|
11484
11623
|
name: "write_integrity",
|
|
11485
|
-
status: phantoms.length ? "
|
|
11486
|
-
detail: phantoms.length ? `${phantoms.length}
|
|
11624
|
+
status: phantoms.length ? "warn" : "pass",
|
|
11625
|
+
detail: phantoms.length ? `${phantoms.length} unexplained write event(s) in last 48h: ${phantoms.slice(0, 3).join(", ")}${phantoms.length > 3 ? " …" : ""} — row missing without an rm event (deleted pre-0.81 or via db sql? otherwise: rollback / different workspace — check receipts' workspace line)` : "recent write events all match stored rows"
|
|
11487
11626
|
});
|
|
11488
11627
|
}
|
|
11489
11628
|
const failed = checks.filter((c) => c.status === "fail").length;
|