ygl-trees 0.76.0 → 0.77.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 +12 -0
- package/README.md +1 -1
- package/dist/apps/cli/src/bin.js +15 -0
- package/dist/bundle/bin.cjs +258 -297
- package/dist/bundle/text-Bce0wNAD.cjs +172 -0
- package/dist/packages/core/database/src/plugin.js +32 -0
- package/dist/packages/knowledge/skills/src/skill-cmds.js +38 -5
- package/dist/packages/ops/tools/src/plugins.js +31 -2
- package/docs/plugin-authoring.md +19 -0
- package/llms.txt +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-C96Ew50Q.cjs");
|
|
2
|
+
//#region packages/core/text/src/text.ts
|
|
3
|
+
var text_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
4
|
+
clip: () => clip,
|
|
5
|
+
contentHash: () => contentHash,
|
|
6
|
+
ngram: () => ngram,
|
|
7
|
+
nowIso: () => nowIso,
|
|
8
|
+
parseFrontmatter: () => parseFrontmatter,
|
|
9
|
+
sha1: () => sha1,
|
|
10
|
+
shortId: () => shortId,
|
|
11
|
+
slug: () => slug,
|
|
12
|
+
splitTags: () => splitTags,
|
|
13
|
+
stampId: () => stampId
|
|
14
|
+
});
|
|
15
|
+
function nowIso() {
|
|
16
|
+
const d = /* @__PURE__ */ new Date();
|
|
17
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
18
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
19
|
+
}
|
|
20
|
+
function stampId() {
|
|
21
|
+
const d = /* @__PURE__ */ new Date();
|
|
22
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
23
|
+
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
24
|
+
}
|
|
25
|
+
function slug(text, limit = 40) {
|
|
26
|
+
const s = (text || "").trim().toLowerCase().replace(new RegExp(`[^\\w${CJK}]+`, "gu"), "-").replace(/^-+|-+$/g, "");
|
|
27
|
+
if (s.length <= limit) return s || "item";
|
|
28
|
+
return s.slice(0, limit).replace(/-+$/, "") || "item";
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Tokenize mixed Chinese/English text for SQLite FTS5.
|
|
32
|
+
*
|
|
33
|
+
* FTS5's default unicode61 tokenizer treats a whole CJK sentence as one token,
|
|
34
|
+
* which makes Chinese search degrade to exact match. We expand CJK runs into
|
|
35
|
+
* character bigrams and keep latin words intact.
|
|
36
|
+
*/
|
|
37
|
+
function ngram(text) {
|
|
38
|
+
const src = (text || "").normalize("NFKC").toLowerCase();
|
|
39
|
+
const out = [];
|
|
40
|
+
const segs = src.match(CJK_RUN_RE) || [];
|
|
41
|
+
for (const seg of segs) if (CJK_ONLY_RE.test(seg)) {
|
|
42
|
+
if (seg.length === 1) out.push(seg);
|
|
43
|
+
else for (let i = 0; i < seg.length - 1; i++) out.push(seg.slice(i, i + 2));
|
|
44
|
+
} else {
|
|
45
|
+
const w = seg.replace(/^[^\w]+|[^\w]+$/gu, "");
|
|
46
|
+
if (w) out.push(w);
|
|
47
|
+
}
|
|
48
|
+
return out.join(" ");
|
|
49
|
+
}
|
|
50
|
+
function sha1(text) {
|
|
51
|
+
return require("node:crypto").createHash("sha1").update(text || "").digest("hex");
|
|
52
|
+
}
|
|
53
|
+
/** Content hash for idempotent writes (single source: store writes and the
|
|
54
|
+
* TypeORM write service must produce identical hashes); --force salts it
|
|
55
|
+
* so a duplicate row is explicitly distinct (queryable via `db dups`). */
|
|
56
|
+
function contentHash(content, force) {
|
|
57
|
+
return sha1(content) + (force ? `#f${Date.now()}${Math.random()}` : "");
|
|
58
|
+
}
|
|
59
|
+
function shortId(parts, n = 8) {
|
|
60
|
+
return sha1(parts.join("|") + Date.now() + Math.random()).slice(0, n);
|
|
61
|
+
}
|
|
62
|
+
/** Truncate for table display, collapsing newlines. */
|
|
63
|
+
function clip(text, n = 40) {
|
|
64
|
+
const one = (text || "").replace(/\s+/g, " ").trim();
|
|
65
|
+
return one.length <= n ? one : one.slice(0, Math.max(1, n - 1)) + "…";
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Minimal SKILL.md frontmatter parser: supports `key: value`, quoted scalars,
|
|
69
|
+
* folded blocks (`>-` / `>`) and inline `a,b` lists.
|
|
70
|
+
*/
|
|
71
|
+
function parseFrontmatter(raw) {
|
|
72
|
+
const text = raw.replace(/^/, "");
|
|
73
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
|
|
74
|
+
if (!m) return {
|
|
75
|
+
meta: {},
|
|
76
|
+
body: text
|
|
77
|
+
};
|
|
78
|
+
const meta = {};
|
|
79
|
+
const lines = m[1].split(/\r?\n/);
|
|
80
|
+
let key = null;
|
|
81
|
+
let folded = null;
|
|
82
|
+
const flush = () => {
|
|
83
|
+
if (key && folded) meta[key] = folded.join(" ").replace(/\s+/g, " ").trim();
|
|
84
|
+
key = null;
|
|
85
|
+
folded = null;
|
|
86
|
+
};
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
const kv = /^([A-Za-z_][\w-]*)\s*:\s*(.*)$/.exec(line);
|
|
89
|
+
if (kv && !/^\s/.test(line)) {
|
|
90
|
+
flush();
|
|
91
|
+
key = kv[1];
|
|
92
|
+
const v = kv[2].trim();
|
|
93
|
+
if (v === ">-" || v === ">" || v === "|" || v === "|-") folded = [];
|
|
94
|
+
else {
|
|
95
|
+
meta[key] = v.replace(/^["']|["']$/g, "");
|
|
96
|
+
key = null;
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (key && folded) folded.push(line.trim());
|
|
101
|
+
}
|
|
102
|
+
flush();
|
|
103
|
+
return {
|
|
104
|
+
meta,
|
|
105
|
+
body: text.slice(m[0].length)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function splitTags(v) {
|
|
109
|
+
return (v || "").split(/[,,;;\s]+/).map((s) => s.trim()).filter(Boolean);
|
|
110
|
+
}
|
|
111
|
+
var CJK, CJK_RUN_RE, CJK_ONLY_RE;
|
|
112
|
+
var init_text = require_rolldown_runtime.__esmMin((() => {
|
|
113
|
+
CJK = "\\u4e00-\\u9fff\\u3400-\\u4dbf\\uf900-\\ufaff";
|
|
114
|
+
new RegExp(`[${CJK}]`, "u");
|
|
115
|
+
CJK_RUN_RE = new RegExp(`[${CJK}]+|[^\\s${CJK}]+`, "gu");
|
|
116
|
+
CJK_ONLY_RE = new RegExp(`^[${CJK}]+$`, "u");
|
|
117
|
+
}));
|
|
118
|
+
//#endregion
|
|
119
|
+
Object.defineProperty(exports, "clip", {
|
|
120
|
+
enumerable: true,
|
|
121
|
+
get: function() {
|
|
122
|
+
return clip;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
Object.defineProperty(exports, "contentHash", {
|
|
126
|
+
enumerable: true,
|
|
127
|
+
get: function() {
|
|
128
|
+
return contentHash;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
Object.defineProperty(exports, "init_text", {
|
|
132
|
+
enumerable: true,
|
|
133
|
+
get: function() {
|
|
134
|
+
return init_text;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
Object.defineProperty(exports, "ngram", {
|
|
138
|
+
enumerable: true,
|
|
139
|
+
get: function() {
|
|
140
|
+
return ngram;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
Object.defineProperty(exports, "nowIso", {
|
|
144
|
+
enumerable: true,
|
|
145
|
+
get: function() {
|
|
146
|
+
return nowIso;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
Object.defineProperty(exports, "parseFrontmatter", {
|
|
150
|
+
enumerable: true,
|
|
151
|
+
get: function() {
|
|
152
|
+
return parseFrontmatter;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
Object.defineProperty(exports, "sha1", {
|
|
156
|
+
enumerable: true,
|
|
157
|
+
get: function() {
|
|
158
|
+
return sha1;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
Object.defineProperty(exports, "shortId", {
|
|
162
|
+
enumerable: true,
|
|
163
|
+
get: function() {
|
|
164
|
+
return shortId;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
Object.defineProperty(exports, "text_exports", {
|
|
168
|
+
enumerable: true,
|
|
169
|
+
get: function() {
|
|
170
|
+
return text_exports;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
@@ -49,4 +49,36 @@ class DatabaseService extends cordis_1.Service {
|
|
|
49
49
|
exports.DatabaseService = DatabaseService;
|
|
50
50
|
function apply(ctx, config) {
|
|
51
51
|
new DatabaseService(ctx, config.file);
|
|
52
|
+
// 0.76 bridge: eventbus (internal) → ctx.emit (Cordis public API).
|
|
53
|
+
// This makes "ctx.on()/emit()" work as documented — plugins can listen
|
|
54
|
+
// with ctx.on("ygl", handler) or ctx.on("ygl:node.unlock", handler).
|
|
55
|
+
// The eventbus remains the internal write-path transport.
|
|
56
|
+
try {
|
|
57
|
+
const { onEvent, onEventKind } = require("../../../core/text/src/eventbus");
|
|
58
|
+
const unsub = onEvent((evt) => { try {
|
|
59
|
+
ctx.emit("ygl", evt);
|
|
60
|
+
}
|
|
61
|
+
catch { /* */ } });
|
|
62
|
+
// Also bridge kind-specific channels
|
|
63
|
+
const kindSubs = [unsub];
|
|
64
|
+
const knownKinds = ["node.unlock", "node.lock", "node.add", "node.link",
|
|
65
|
+
"kb.add", "kb.rm", "claim.resolve", "claim.add", "task.status",
|
|
66
|
+
"project.new", "skill.add"];
|
|
67
|
+
for (const kind of knownKinds) {
|
|
68
|
+
kindSubs.push(onEventKind(kind, (evt) => { try {
|
|
69
|
+
ctx.emit(`ygl:${kind}`, evt);
|
|
70
|
+
}
|
|
71
|
+
catch { /* */ } }));
|
|
72
|
+
}
|
|
73
|
+
// Cleanup on Context dispose (best-effort — this fork has no ctx.dispose)
|
|
74
|
+
ctx.fiber?.collect?.(() => {
|
|
75
|
+
for (const u of kindSubs) {
|
|
76
|
+
try {
|
|
77
|
+
u();
|
|
78
|
+
}
|
|
79
|
+
catch { /* */ }
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch { /* eventbus optional */ }
|
|
52
84
|
}
|
|
@@ -187,13 +187,46 @@ function run(env, action, rest, args) {
|
|
|
187
187
|
]);
|
|
188
188
|
return 0;
|
|
189
189
|
case "link": {
|
|
190
|
-
// One-command symlink: skills/ →
|
|
191
|
-
//
|
|
190
|
+
// One-command symlink: skills/ → AI agent's skill directory.
|
|
191
|
+
// Supported targets:
|
|
192
|
+
// (default) ./.agents/skills/ (project-level, DSH/Claude)
|
|
193
|
+
// --global ~/.agents/skills/ (user-level, DSH/Claude)
|
|
194
|
+
// --ai pi ~/.pi/agent/skills/ (Pi agent)
|
|
195
|
+
// --ai claude ~/.claude/skills/ (Claude Code)
|
|
196
|
+
// --ai cursor ~/.cursor/skills/ (Cursor IDE)
|
|
197
|
+
// --ai custom --to <path> (any directory)
|
|
192
198
|
const os = require("node:os");
|
|
193
199
|
const globalFlag = args?.flags?.global === true;
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
200
|
+
const aiName = String(args?.flags?.ai || "").toLowerCase();
|
|
201
|
+
const customTo = String(args?.flags?.to || "");
|
|
202
|
+
// AI agent skill directory registry
|
|
203
|
+
const AI_SKILL_DIRS = {
|
|
204
|
+
pi: () => path.join(os.homedir(), ".pi", "agent", "skills"),
|
|
205
|
+
claude: () => path.join(os.homedir(), ".claude", "skills"),
|
|
206
|
+
cursor: () => path.join(os.homedir(), ".cursor", "skills"),
|
|
207
|
+
windsurf: () => path.join(os.homedir(), ".windsurf", "skills"),
|
|
208
|
+
};
|
|
209
|
+
let targetDir;
|
|
210
|
+
if (customTo) {
|
|
211
|
+
// --ai custom --to <path>
|
|
212
|
+
targetDir = path.resolve(customTo);
|
|
213
|
+
}
|
|
214
|
+
else if (aiName && AI_SKILL_DIRS[aiName]) {
|
|
215
|
+
// --ai <known-agent>
|
|
216
|
+
targetDir = AI_SKILL_DIRS[aiName]();
|
|
217
|
+
}
|
|
218
|
+
else if (aiName && aiName !== "custom") {
|
|
219
|
+
// --ai <unknown> → error with suggestions
|
|
220
|
+
(0, fmt_1.bad)(ctx, `unknown AI agent: ${aiName}`);
|
|
221
|
+
(0, fmt_1.out)(ctx, ` supported: ${Object.keys(AI_SKILL_DIRS).join(", ")} | custom (with --to <path>)`);
|
|
222
|
+
return 2;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
// default or --global
|
|
226
|
+
targetDir = globalFlag
|
|
227
|
+
? path.join(os.homedir(), ".agents", "skills")
|
|
228
|
+
: path.join(process.cwd(), ".agents", "skills");
|
|
229
|
+
}
|
|
197
230
|
// Find source skills directory (probe upward from cwd, same as repoRoot)
|
|
198
231
|
let skillsDir = null;
|
|
199
232
|
for (let dir = process.cwd(), i = 0; i < 6; i++, dir = path.dirname(dir)) {
|
|
@@ -162,9 +162,16 @@ function pnpm(home, args) {
|
|
|
162
162
|
// there. Requires pnpm >= 9.4.
|
|
163
163
|
const pm = process.env.YGL_PACKAGE_MANAGER || "pnpm";
|
|
164
164
|
if (process.platform === "win32") {
|
|
165
|
-
const q = (s) => `"${String(s).replace(/["&|<>^]/g, "")}"`;
|
|
166
165
|
const exe = resolvePm(pm);
|
|
167
|
-
|
|
166
|
+
// DEP0190 fix (a2a 7aa5c871 cosmetic): avoid shell:true with args —
|
|
167
|
+
// invoke cmd explicitly instead, args are properly escaped by spawnSync.
|
|
168
|
+
const { spawnSync } = require("node:child_process");
|
|
169
|
+
const r = spawnSync("cmd", ["/c", exe, ...args, "--dir", profileDir(home), "--ignore-workspace"], { stdio: "pipe" });
|
|
170
|
+
if (r.status !== 0) {
|
|
171
|
+
const err = new Error(String(r.stderr || r.stdout || `pnpm exited ${r.status}`));
|
|
172
|
+
err.status = r.status;
|
|
173
|
+
throw err;
|
|
174
|
+
}
|
|
168
175
|
return;
|
|
169
176
|
}
|
|
170
177
|
(0, node_child_process_1.execFileSync)(pm, [...args, "--dir", profileDir(home), "--ignore-workspace"], { stdio: "pipe" });
|
|
@@ -194,6 +201,28 @@ const q2 = (s) => `"${String(s).replace(/["&|<>^]/g, "")}"`;
|
|
|
194
201
|
async function installPlugin(home, spec) {
|
|
195
202
|
initProfile(home);
|
|
196
203
|
const abs = path.resolve(spec);
|
|
204
|
+
// P0 fix (a2a 7aa5c871): validate local paths BEFORE recording anything —
|
|
205
|
+
// a bad path previously degraded to basename "plugin" and bricked the home.
|
|
206
|
+
if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
|
|
207
|
+
// P2 fix: read the real package name, never trust basename
|
|
208
|
+
const pkgPath = path.join(abs, "package.json");
|
|
209
|
+
if (!fs.existsSync(pkgPath)) {
|
|
210
|
+
throw new Error(`local plugin directory has no package.json: ${abs}`);
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
214
|
+
if (!pkg.name || typeof pkg.name !== "string") {
|
|
215
|
+
throw new Error(`package.json missing "name" field: ${abs}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
throw new Error(`invalid package.json in ${abs}: ${e?.message || e}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else if (spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("\\") || /^[A-Z]:/.test(spec)) {
|
|
223
|
+
// Looks like a path but doesn't exist — reject before recording
|
|
224
|
+
throw new Error(`plugin path not found: ${abs}`);
|
|
225
|
+
}
|
|
197
226
|
const installSpec = fs.existsSync(abs) && fs.statSync(abs).isDirectory() ? `link:${abs}` : spec;
|
|
198
227
|
const existing = readUserBundle(home).find((p) => p.spec === installSpec || p.spec === spec);
|
|
199
228
|
if (existing)
|
package/docs/plugin-authoring.md
CHANGED
|
@@ -93,3 +93,22 @@ repo-root/
|
|
|
93
93
|
| `plugin list` 显示 ✗ 不可解析 | `plugin remove <name>` 后重装;或 `doctor` 看 plugin_profile 项 |
|
|
94
94
|
| 启动报 "N plugin issue(s) during startup" | 读终端给出的 report 路径(`$YGL_HOME/logs/startup-*.log`) |
|
|
95
95
|
| 装了命令组不生效 | 命令组在**下次进程启动**时注册(CLI 每命令独立进程,自然生效) |
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
## 事件监听(0.76+)
|
|
99
|
+
|
|
100
|
+
插件可以通过 Cordis 标准 `ctx.on` 监听跨域事件:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// 监听全部事件
|
|
104
|
+
ctx.on("ygl", (evt) => {
|
|
105
|
+
console.log(evt.kind, evt.entityId); // e.g. "node.unlock" "sn123"
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// 监听特定类型
|
|
109
|
+
ctx.on("ygl:node.unlock", (evt) => { /* ... */ });
|
|
110
|
+
ctx.on("ygl:kb.add", (evt) => { /* ... */ });
|
|
111
|
+
ctx.on("ygl:claim.resolve", (evt) => { /* ... */ });
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
事件在 logEvent 落表后即时发出(事件驱动 SSE 同通道)。支持的事件类型见 `packages/core/text/src/eventbus.ts`。
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ygl-trees v0.
|
|
1
|
+
# ygl-trees v0.76.1 — AI Agent Research Workbench
|
|
2
2
|
|
|
3
3
|
> Single SQLite source of truth. Research → Production → Knowledge Reuse + Tech Tree.
|
|
4
4
|
> CLI for AI agents: every command supports --json (machine-readable, including errors).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ygl-trees",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.77.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",
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"node-sqlite3-wasm": "^0.8.61",
|
|
26
26
|
"reflect-metadata": "^0.2.2",
|
|
27
27
|
"typeorm": "^1.1.2",
|
|
28
|
-
"@deepseek-ai/cordis-plugin-timer": "~1.1.7-alpha.2",
|
|
29
28
|
"@deepseek-ai/cordis": "~4.0.5-alpha.2",
|
|
30
|
-
"@deepseek-ai/
|
|
31
|
-
"@deepseek-ai/
|
|
29
|
+
"@deepseek-ai/schemastery": "~3.18.5-alpha.2",
|
|
30
|
+
"@deepseek-ai/cordis-plugin-timer": "~1.1.7-alpha.2",
|
|
31
|
+
"@deepseek-ai/cosmokit": "~1.8.6-alpha.2"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^20.0.0",
|