ygl-trees 0.66.0 → 0.69.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 +22 -0
- package/dist/apps/cli/src/bin.js +47 -4
- package/dist/apps/cli/src/profile-boot.js +11 -3
- package/dist/bundle/bin.cjs +159 -124
- package/dist/bundle/mcp-C27lIHd1.cjs +276 -0
- package/dist/bundle/rolldown-runtime-C96Ew50Q.cjs +64 -0
- package/dist/packages/core/database/src/db.js +421 -379
- package/dist/packages/core/database/src/plugin.js +5 -3
- package/dist/packages/core/database/src/types.js +8 -2
- package/dist/packages/ops/tools/src/doctor.js +19 -7
- package/dist/packages/ops/tools/src/index.js +4 -1
- package/dist/packages/ops/tools/src/mcp.js +233 -0
- package/dist/packages/ops/tools/src/plugins.js +10 -2
- package/dist/packages/research/pipeline/src/entities.js +6 -0
- package/dist/packages/research/pipeline/src/writes.js +1 -1
- package/dist/packages/techtree/model/src/writes.js +14 -14
- package/llms.txt +1 -1
- package/package.json +3 -3
|
@@ -17,9 +17,11 @@ class DatabaseService extends cordis_1.Service {
|
|
|
17
17
|
// GPT-6.1 SOL fix: the DB lock was only applied in the profile-boot
|
|
18
18
|
// fallback path, but the base bundle always instantiates this Service —
|
|
19
19
|
// the primary path never locked. Lock here (the actual entry point for
|
|
20
|
-
// every CLI invocation), preserving
|
|
21
|
-
// test execution
|
|
22
|
-
|
|
20
|
+
// every CLI invocation), preserving bypasses:
|
|
21
|
+
// YGL_TEST=1 — parallel test execution
|
|
22
|
+
// YGL_SERVE=1 — webui serve (long-lived; WAL permits concurrent access)
|
|
23
|
+
const lock = process.env.YGL_TEST !== "1" && process.env.YGL_SERVE !== "1";
|
|
24
|
+
this.db = new db_1.DB(dbFile, { lock });
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
exports.DatabaseService = DatabaseService;
|
|
@@ -14,8 +14,14 @@ function parse(argv) {
|
|
|
14
14
|
const flags = {};
|
|
15
15
|
let rest = false;
|
|
16
16
|
const takesValue = (name) => !BOOLEAN_FLAGS.has(name);
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/** A token is a flag boundary only if this parser could itself consume it as
|
|
18
|
+
* a flag: `--`, a long `--name`, or a single-letter `-x`. Any other token
|
|
19
|
+
* starting with `-` (e.g. `- 双平台…`, `-1`, `-abc`) can never be parsed as
|
|
20
|
+
* a flag, so a value-taking flag safely claims it — `--quote "- text"`
|
|
21
|
+
* keeps its value instead of collapsing to `true` and leaking the text
|
|
22
|
+
* into positionals. */
|
|
23
|
+
const looksLikeFlag = (s) => s === "--" || s.startsWith("--") || /^-[A-Za-z]$/.test(s);
|
|
24
|
+
const isValue = (s) => !looksLikeFlag(s);
|
|
19
25
|
for (let i = 0; i < argv.length; i++) {
|
|
20
26
|
const a = argv[i];
|
|
21
27
|
if (rest) {
|
|
@@ -137,9 +137,16 @@ function run(env, action, rest, args) {
|
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
139
|
// ── 7. Stale lock check ──
|
|
140
|
+
// The CLI process running doctor holds its own .ygllock while checking —
|
|
141
|
+
// exclude our own live lock; warn only on foreign/stale holders.
|
|
140
142
|
const dbPath = db.path;
|
|
141
|
-
const lockPath = `${dbPath}.
|
|
142
|
-
|
|
143
|
+
const lockPath = `${dbPath}.ygllock`;
|
|
144
|
+
let lockHolder = "";
|
|
145
|
+
try {
|
|
146
|
+
lockHolder = require("node:fs").readFileSync(`${lockPath}/pid`, "utf8").trim();
|
|
147
|
+
}
|
|
148
|
+
catch { /* absent/unreadable */ }
|
|
149
|
+
const hasLock = require("node:fs").existsSync(lockPath) && lockHolder !== String(process.pid);
|
|
143
150
|
checks.push({
|
|
144
151
|
name: "stale_lock",
|
|
145
152
|
status: hasLock ? "warn" : "pass",
|
|
@@ -151,8 +158,8 @@ function run(env, action, rest, args) {
|
|
|
151
158
|
// A malformed envelope silently degrades: evalChoice/evalScore fall back to
|
|
152
159
|
// empty options/levels → the gate can never resolve (always pending).
|
|
153
160
|
{
|
|
154
|
-
const evalConds = db.all(`SELECT c.id, c.name, c.op, c.value, n.key AS node
|
|
155
|
-
FROM node_conditions c JOIN nodes n ON n.id=c.node_id
|
|
161
|
+
const evalConds = db.all(`SELECT c.id, c.name, c.op, c.value, n.key AS node
|
|
162
|
+
FROM node_conditions c JOIN nodes n ON n.id=c.node_id
|
|
156
163
|
WHERE c.op IN ('eval','eval-choice','eval-score')`);
|
|
157
164
|
const bad = [];
|
|
158
165
|
for (const c of evalConds) {
|
|
@@ -240,9 +247,14 @@ function run(env, action, rest, args) {
|
|
|
240
247
|
db.run("UPDATE meta SET value='0' WHERE key='index_dirty'");
|
|
241
248
|
fixes.push("index dirty flag cleared (run: ygl-trees node index)");
|
|
242
249
|
}
|
|
243
|
-
// stale lock → remove
|
|
244
|
-
const lockDir = `${env.db.path}.
|
|
245
|
-
|
|
250
|
+
// stale lock → remove (never our own live lock)
|
|
251
|
+
const lockDir = `${env.db.path}.ygllock`;
|
|
252
|
+
let holder = "";
|
|
253
|
+
try {
|
|
254
|
+
holder = fs.readFileSync(`${lockDir}/pid`, "utf8").trim();
|
|
255
|
+
}
|
|
256
|
+
catch { /* */ }
|
|
257
|
+
if (fs.existsSync(lockDir) && holder !== String(process.pid)) {
|
|
246
258
|
try {
|
|
247
259
|
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
248
260
|
fixes.push("stale lock removed");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.apply = exports.OpsService = exports.MIRROR_TABLES = exports.migrateToPg = exports.syncToPg = exports.configAll = exports.configReset = exports.configSet = exports.configGetBool = exports.configGetNumber = exports.configGet = exports.CONFIG_REGISTRY = void 0;
|
|
3
|
+
exports.buildTools = exports.serveMcp = exports.apply = exports.OpsService = exports.MIRROR_TABLES = exports.migrateToPg = exports.syncToPg = exports.configAll = exports.configReset = exports.configSet = exports.configGetBool = exports.configGetNumber = exports.configGet = exports.CONFIG_REGISTRY = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* @module @ygl/ops-tools
|
|
6
6
|
*/
|
|
@@ -20,3 +20,6 @@ Object.defineProperty(exports, "MIRROR_TABLES", { enumerable: true, get: functio
|
|
|
20
20
|
var plugin_1 = require("./plugin");
|
|
21
21
|
Object.defineProperty(exports, "OpsService", { enumerable: true, get: function () { return plugin_1.OpsService; } });
|
|
22
22
|
Object.defineProperty(exports, "apply", { enumerable: true, get: function () { return plugin_1.apply; } });
|
|
23
|
+
var mcp_1 = require("./mcp");
|
|
24
|
+
Object.defineProperty(exports, "serveMcp", { enumerable: true, get: function () { return mcp_1.serveMcp; } });
|
|
25
|
+
Object.defineProperty(exports, "buildTools", { enumerable: true, get: function () { return mcp_1.buildTools; } });
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.buildTools = buildTools;
|
|
37
|
+
exports.serveMcp = serveMcp;
|
|
38
|
+
/**
|
|
39
|
+
* MCP (Model Context Protocol) Server — stdio transport.
|
|
40
|
+
*
|
|
41
|
+
* Eight-tool surface designed by the unified three-pillar theory (R8 research):
|
|
42
|
+
* agent_brief · agent_match · kb_search · claim_evaluate
|
|
43
|
+
* tree_state · produce_report · doctor · task_advance
|
|
44
|
+
*
|
|
45
|
+
* Each tool routes to the CLI --json backend, inheriting its frozen contracts
|
|
46
|
+
* (idempotent writes, pure JSON errors, snake_case fields). Agents call tools
|
|
47
|
+
* directly — no command-line parsing layer.
|
|
48
|
+
*
|
|
49
|
+
* Protocol: JSON-RPC 2.0 over stdio (one message per line).
|
|
50
|
+
* → {"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}
|
|
51
|
+
* ← {"jsonrpc":"2.0","id":1,"result":{...}}
|
|
52
|
+
*
|
|
53
|
+
* @module @ygl/ops-tools/mcp
|
|
54
|
+
*/
|
|
55
|
+
const readline = __importStar(require("node:readline"));
|
|
56
|
+
/** Build a CLI invocation and capture JSON stdout.
|
|
57
|
+
* Uses process.argv[1] (the running script — bundle or source) to re-invoke
|
|
58
|
+
* the CLI as a child process. This works in both dev (dist/apps/...) and
|
|
59
|
+
* bundle (dist/bundle/bin.cjs) contexts. */
|
|
60
|
+
function cliJson(parts) {
|
|
61
|
+
const { spawn } = require("node:child_process");
|
|
62
|
+
const self = process.argv[1]; // the bin.cjs or bin.js that's running this MCP server
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const child = spawn(process.execPath, [self, "--json", ...parts], {
|
|
65
|
+
env: { ...process.env, YGL_SERVE: "1", YGL_MCP_CHILD: "1" },
|
|
66
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
67
|
+
});
|
|
68
|
+
let out = "";
|
|
69
|
+
let err = "";
|
|
70
|
+
child.stdout.on("data", (d) => (out += d));
|
|
71
|
+
child.stderr.on("data", (d) => (err += d));
|
|
72
|
+
child.on("close", (code) => {
|
|
73
|
+
if (out.trim()) {
|
|
74
|
+
try {
|
|
75
|
+
resolve(JSON.parse(out));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
catch { /* fall through */ }
|
|
79
|
+
}
|
|
80
|
+
reject(new Error(`CLI rc=${code}: ${err.slice(0, 200) || out.slice(0, 200)}`));
|
|
81
|
+
});
|
|
82
|
+
child.on("error", (e) => reject(e));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function buildTools() {
|
|
86
|
+
return [
|
|
87
|
+
{
|
|
88
|
+
name: "agent_brief",
|
|
89
|
+
description: "Current workspace state: project, progress, counts, next actions, available skills. Always start here.",
|
|
90
|
+
inputSchema: { type: "object", properties: {} },
|
|
91
|
+
execute: () => cliJson(["agent", "brief"]),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "agent_match",
|
|
95
|
+
description: "Discover which skill handles a task. Returns matched skills with relevance scores.",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: { query: { type: "string", description: "Task description to match" } },
|
|
99
|
+
required: ["query"],
|
|
100
|
+
},
|
|
101
|
+
execute: (a) => cliJson(["agent", "match", String(a.query)]),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "kb_search",
|
|
105
|
+
description: "Search knowledge notes (CJK full-text). Returns notes with title, body, tags, confidence.",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
query: { type: "string", description: "Search query (Chinese supported)" },
|
|
110
|
+
eval: { type: "boolean", description: "Enable decision-model semantic re-ranking" },
|
|
111
|
+
},
|
|
112
|
+
required: ["query"],
|
|
113
|
+
},
|
|
114
|
+
execute: (a) => {
|
|
115
|
+
const parts = ["kb", "search", String(a.query)];
|
|
116
|
+
if (a.eval)
|
|
117
|
+
parts.push("--eval");
|
|
118
|
+
return cliJson(parts);
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "claim_evaluate",
|
|
123
|
+
description: "Decision model evaluates a claim's confidence (score 0-5 → grade A/B/C/D).",
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: { claim_id: { type: "string", description: "Claim ID to evaluate" } },
|
|
127
|
+
required: ["claim_id"],
|
|
128
|
+
},
|
|
129
|
+
execute: (a) => cliJson(["research", "claim", "evaluate", String(a.claim_id)]),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "tree_state",
|
|
133
|
+
description: "All trees with node states (locked/unlocked). Visualize the dependency graph.",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: { tree: { type: "string", description: "Optional tree name (omit for all)" } },
|
|
137
|
+
},
|
|
138
|
+
execute: (a) => cliJson(["node", "list", ...(a.tree ? [String(a.tree)] : [])]),
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "produce_report",
|
|
142
|
+
description: "Assemble claims + evidence into a markdown report. Returns report ID and content.",
|
|
143
|
+
inputSchema: { type: "object", properties: {} },
|
|
144
|
+
execute: () => cliJson(["produce", "report"]),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "doctor",
|
|
148
|
+
description: "9-item workspace health check. Pass fix=true to auto-fix safe issues.",
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: { fix: { type: "boolean", description: "Auto-fix dirty index + stale locks" } },
|
|
152
|
+
},
|
|
153
|
+
execute: (a) => cliJson(["doctor", ...(a.fix ? ["--fix"] : [])]),
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "task_advance",
|
|
157
|
+
description: "Advance a task through the state machine: todo → doing → done (or roll back).",
|
|
158
|
+
inputSchema: {
|
|
159
|
+
type: "object",
|
|
160
|
+
properties: {
|
|
161
|
+
task_id: { type: "string", description: "Task artifact ID" },
|
|
162
|
+
action: { type: "string", enum: ["doing", "done", "todo"], description: "Target status" },
|
|
163
|
+
},
|
|
164
|
+
required: ["task_id", "action"],
|
|
165
|
+
},
|
|
166
|
+
execute: (a) => cliJson(["task", String(a.action), String(a.task_id)]),
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
/** Run the MCP server on stdio. */
|
|
171
|
+
function serveMcp() {
|
|
172
|
+
const tools = buildTools();
|
|
173
|
+
const rl = readline.createInterface({ input: process.stdin });
|
|
174
|
+
const respond = (id, result) => {
|
|
175
|
+
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n");
|
|
176
|
+
};
|
|
177
|
+
const respondErr = (id, code, message) => {
|
|
178
|
+
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }) + "\n");
|
|
179
|
+
};
|
|
180
|
+
rl.on("line", (line) => {
|
|
181
|
+
if (!line.trim())
|
|
182
|
+
return;
|
|
183
|
+
let msg;
|
|
184
|
+
try {
|
|
185
|
+
msg = JSON.parse(line);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const { id, method, params } = msg;
|
|
191
|
+
switch (method) {
|
|
192
|
+
case "initialize":
|
|
193
|
+
respond(id, {
|
|
194
|
+
protocolVersion: "2024-11-05",
|
|
195
|
+
capabilities: { tools: {} },
|
|
196
|
+
serverInfo: { name: "ygl-trees", version: "0.69.0" },
|
|
197
|
+
});
|
|
198
|
+
break;
|
|
199
|
+
case "tools/list":
|
|
200
|
+
respond(id, {
|
|
201
|
+
tools: tools.map((t) => ({
|
|
202
|
+
name: t.name,
|
|
203
|
+
description: t.description,
|
|
204
|
+
inputSchema: t.inputSchema,
|
|
205
|
+
})),
|
|
206
|
+
});
|
|
207
|
+
break;
|
|
208
|
+
case "tools/call": {
|
|
209
|
+
const name = params?.name;
|
|
210
|
+
const tool = tools.find((t) => t.name === name);
|
|
211
|
+
if (!tool) {
|
|
212
|
+
respondErr(id, -32602, `Unknown tool: ${name}`);
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
tool
|
|
216
|
+
.execute(params?.arguments || {})
|
|
217
|
+
.then((data) => respond(id, { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }))
|
|
218
|
+
.catch((e) => respond(id, {
|
|
219
|
+
content: [{ type: "text", text: `Error: ${e?.message || e}` }],
|
|
220
|
+
isError: true,
|
|
221
|
+
}));
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case "ping":
|
|
225
|
+
respond(id, {});
|
|
226
|
+
break;
|
|
227
|
+
default:
|
|
228
|
+
if (id !== undefined)
|
|
229
|
+
respondErr(id, -32601, `Method not found: ${method}`);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
process.stderr.write("[ygl-trees MCP] stdio server ready — 8 tools\n");
|
|
233
|
+
}
|
|
@@ -152,14 +152,22 @@ function pnpm(home, args) {
|
|
|
152
152
|
// embedded/offline carriers without touching this module. PATH may carry
|
|
153
153
|
// broken .bin shims (observed: a parent node_modules shim pointing at a
|
|
154
154
|
// missing pnpm.cjs under vitest) — resolve by probing, not first-hit.
|
|
155
|
+
//
|
|
156
|
+
// --ignore-workspace: pnpm walks UP from --dir to the nearest
|
|
157
|
+
// pnpm-workspace.yaml. When the profile lives inside a workspace (the unit
|
|
158
|
+
// tests' tmp/ profiles — or any repo-embedded home), the profile registered
|
|
159
|
+
// as a workspace importer and leaked tmp/plugins-unit-* blocks into the
|
|
160
|
+
// repo lockfile (committed that way through 0.66.0). Standalone profiles
|
|
161
|
+
// (the real $YGL_HOME) are outside any workspace, so this flag is a no-op
|
|
162
|
+
// there. Requires pnpm >= 9.4.
|
|
155
163
|
const pm = process.env.YGL_PACKAGE_MANAGER || "pnpm";
|
|
156
164
|
if (process.platform === "win32") {
|
|
157
165
|
const q = (s) => `"${String(s).replace(/["&|<>^]/g, "")}"`;
|
|
158
166
|
const exe = resolvePm(pm);
|
|
159
|
-
(0, node_child_process_1.execFileSync)([q(exe), ...args.map(q), "--dir", q(profileDir(home))].join(" "), { stdio: "pipe", shell: true });
|
|
167
|
+
(0, node_child_process_1.execFileSync)([q(exe), ...args.map(q), "--dir", q(profileDir(home)), "--ignore-workspace"].join(" "), { stdio: "pipe", shell: true });
|
|
160
168
|
return;
|
|
161
169
|
}
|
|
162
|
-
(0, node_child_process_1.execFileSync)(pm, [...args, "--dir", profileDir(home)], { stdio: "pipe" });
|
|
170
|
+
(0, node_child_process_1.execFileSync)(pm, [...args, "--dir", profileDir(home), "--ignore-workspace"], { stdio: "pipe" });
|
|
163
171
|
}
|
|
164
172
|
let cachedPm;
|
|
165
173
|
function resolvePm(pm) {
|
|
@@ -338,6 +338,11 @@ let Claim = class Claim {
|
|
|
338
338
|
contentHash;
|
|
339
339
|
created;
|
|
340
340
|
updated;
|
|
341
|
+
// The @ManyToOne here was missing (likely lost in an early edit): Claim.project
|
|
342
|
+
// was an undecorated property, so Project.claims' inverseSide never resolved
|
|
343
|
+
// and every ORM find loading the claims relation threw
|
|
344
|
+
// "Cannot read properties of undefined (reading 'joinColumns')" —
|
|
345
|
+
// `project show` / demo have been broken on this since the TypeORM pilot.
|
|
341
346
|
project;
|
|
342
347
|
claimEvidence;
|
|
343
348
|
};
|
|
@@ -375,6 +380,7 @@ __decorate([
|
|
|
375
380
|
__metadata("design:type", String)
|
|
376
381
|
], Claim.prototype, "updated", void 0);
|
|
377
382
|
__decorate([
|
|
383
|
+
(0, typeorm_1.ManyToOne)(() => Project, (p) => p.claims, { createForeignKeyConstraints: false }),
|
|
378
384
|
(0, typeorm_1.JoinColumn)({ name: "project_id" }),
|
|
379
385
|
__metadata("design:type", Project)
|
|
380
386
|
], Claim.prototype, "project", void 0);
|
|
@@ -344,7 +344,7 @@ async function taskSetStatus(ds, taskId, status) {
|
|
|
344
344
|
}
|
|
345
345
|
catch { /* malformed → reset */ }
|
|
346
346
|
const from = meta.status || "todo";
|
|
347
|
-
await mgr.query(`UPDATE artifacts SET meta=? WHERE id=?`, [JSON.stringify({ ...meta, status }), taskId]);
|
|
347
|
+
await mgr.query(`UPDATE artifacts SET meta=?, updated=? WHERE id=?`, [JSON.stringify({ ...meta, status }), (0, text_1.nowIso)(), taskId]);
|
|
348
348
|
await logEvent(mgr, "task.status", "task", taskId, `${from} → ${status}`, status === "done" ? "info" : "info");
|
|
349
349
|
return { id: taskId, title: rows[0].title, from, to: status };
|
|
350
350
|
});
|
|
@@ -134,8 +134,8 @@ async function buildIndex(mgr, treeId) {
|
|
|
134
134
|
inDegree[n.key] = (requiredParents.get(n.id) || []).length;
|
|
135
135
|
for (const n of nodes)
|
|
136
136
|
await mgr.query(`UPDATE nodes SET tier=? WHERE id=? AND tier<>?`, [tierOf.get(n.id) ?? 0, n.id, tierOf.get(n.id) ?? 0]);
|
|
137
|
-
await mgr.query(`INSERT INTO node_index(tree_id,topo_order,tiers,in_degree,built_at,dirty) VALUES(?,?,?,?,?,0)
|
|
138
|
-
ON CONFLICT(tree_id) DO UPDATE SET topo_order=excluded.topo_order, tiers=excluded.tiers,
|
|
137
|
+
await mgr.query(`INSERT INTO node_index(tree_id,topo_order,tiers,in_degree,built_at,dirty) VALUES(?,?,?,?,?,0)
|
|
138
|
+
ON CONFLICT(tree_id) DO UPDATE SET topo_order=excluded.topo_order, tiers=excluded.tiers,
|
|
139
139
|
in_degree=excluded.in_degree, built_at=excluded.built_at, dirty=0`, [treeId, JSON.stringify(topo), JSON.stringify(tiers), JSON.stringify(inDegree), (0, text_1.nowIso)()]);
|
|
140
140
|
return { topo, tiers, inDegree };
|
|
141
141
|
}
|
|
@@ -222,10 +222,10 @@ async function link(ds, treeId, parentKey, childKey, o = {}) {
|
|
|
222
222
|
const before = (await mgr.query(`SELECT id FROM node_edges WHERE parent_id=? AND child_id=? AND rel=?`, [p[0].id, c[0].id, rel]));
|
|
223
223
|
const duplicate = before.length > 0;
|
|
224
224
|
const id = duplicate ? before[0].id : (0, text_1.shortId)(["edge", p[0].id, c[0].id, rel], 12);
|
|
225
|
-
await mgr.query(`INSERT INTO node_edges(id,tree_id,parent_id,child_id,rel,mode,required,min_level,weight,note,created)
|
|
226
|
-
VALUES(?,?,?,?,?,?,?,?,?,?,?)
|
|
227
|
-
ON CONFLICT(parent_id,child_id,rel) DO UPDATE SET
|
|
228
|
-
mode=excluded.mode, required=excluded.required, min_level=excluded.min_level,
|
|
225
|
+
await mgr.query(`INSERT INTO node_edges(id,tree_id,parent_id,child_id,rel,mode,required,min_level,weight,note,created)
|
|
226
|
+
VALUES(?,?,?,?,?,?,?,?,?,?,?)
|
|
227
|
+
ON CONFLICT(parent_id,child_id,rel) DO UPDATE SET
|
|
228
|
+
mode=excluded.mode, required=excluded.required, min_level=excluded.min_level,
|
|
229
229
|
weight=excluded.weight, note=excluded.note`, [id, treeId, p[0].id, c[0].id, rel, mode, o.required === false ? 0 : 1, o.min_level ?? 1, o.weight ?? 1, o.note || "", (0, text_1.nowIso)()]);
|
|
230
230
|
await rebuildClosure(mgr, treeId);
|
|
231
231
|
await logEvent(mgr, "node.link", id, { tree: treeId, parent: parentKey, child: childKey, rel, mode, required: o.required !== false, duplicate });
|
|
@@ -256,7 +256,7 @@ async function condAdd(ds, treeId, keyOrId, o) {
|
|
|
256
256
|
if (!db_1.COND_KINDS.includes(kind))
|
|
257
257
|
throw new Error(`kind must be one of ${db_1.COND_KINDS.join("|")}`);
|
|
258
258
|
const id = (0, text_1.shortId)(["cond", n[0].id, o.name], 12);
|
|
259
|
-
await mgr.query(`INSERT INTO node_conditions(id,node_id,name,kind,expr,op,value,required,effect,weight,note)
|
|
259
|
+
await mgr.query(`INSERT INTO node_conditions(id,node_id,name,kind,expr,op,value,required,effect,weight,note)
|
|
260
260
|
VALUES(?,?,?,?,?,?,?,?,?,?,?)`, [id, n[0].id, o.name, kind, o.expr || o.name, o.op || ">=", o.value || "", o.required === false ? 0 : 1, o.effect || "gate", o.weight ?? 1, o.note || ""]);
|
|
261
261
|
await markDirty(mgr, treeId);
|
|
262
262
|
await logEvent(mgr, "node.cond", id, { tree: treeId, node: keyOrId, name: o.name, kind, op: o.op || ">=", required: o.required !== false });
|
|
@@ -269,7 +269,7 @@ async function stateSet(ds, treeId, keyOrId, status, context = "default", level
|
|
|
269
269
|
const n = (await mgr.query(`SELECT id FROM nodes WHERE tree_id=? AND (key=? OR id=?)`, [treeId, keyOrId, keyOrId]));
|
|
270
270
|
if (!n.length)
|
|
271
271
|
throw new Error(`node not found: ${keyOrId}`);
|
|
272
|
-
await mgr.query(`INSERT INTO node_state(tree_id,context,node_id,status,level,progress,updated) VALUES(?,?,?,?,?,?,?)
|
|
272
|
+
await mgr.query(`INSERT INTO node_state(tree_id,context,node_id,status,level,progress,updated) VALUES(?,?,?,?,?,?,?)
|
|
273
273
|
ON CONFLICT(tree_id,context,node_id) DO UPDATE SET status=excluded.status, level=excluded.level, updated=excluded.updated`, [treeId, context, n[0].id, status, level, status === "unlocked" ? 1 : 0, (0, text_1.nowIso)()]);
|
|
274
274
|
// R10.1: unlock/lock are the timeline anchors for R11's time axis
|
|
275
275
|
await logEvent(mgr, status === "unlocked" ? "node.unlock" : "node.lock", n[0].id, {
|
|
@@ -414,20 +414,20 @@ async function importTree(ds, spec, opts = {}) {
|
|
|
414
414
|
for (const e of edges) {
|
|
415
415
|
const pid = nodeId.get(String(e.parent));
|
|
416
416
|
const cid = nodeId.get(String(e.child));
|
|
417
|
-
await mgr.query(`INSERT INTO node_edges(id,tree_id,parent_id,child_id,rel,mode,required,min_level,weight,note,created)
|
|
418
|
-
VALUES(
|
|
419
|
-
ON CONFLICT(parent_id,child_id,rel) DO UPDATE SET
|
|
420
|
-
mode=excluded.mode, required=excluded.required, min_level=excluded.min_level,
|
|
417
|
+
await mgr.query(`INSERT INTO node_edges(id,tree_id,parent_id,child_id,rel,mode,required,min_level,weight,note,created)
|
|
418
|
+
VALUES(?,?,?,?,?,?,?,?,?,?,?)
|
|
419
|
+
ON CONFLICT(parent_id,child_id,rel) DO UPDATE SET
|
|
420
|
+
mode=excluded.mode, required=excluded.required, min_level=excluded.min_level,
|
|
421
421
|
weight=excluded.weight, note=excluded.note`, [
|
|
422
422
|
(0, text_1.shortId)(["edge", tid, pid, cid, String(e.rel)], 12), tid, pid, cid,
|
|
423
423
|
String(e.rel), String(e.mode || "AND"), e.required === false ? 0 : 1,
|
|
424
|
-
Number(e.min_level ?? 1), Number(e.weight ?? 1), String(e.note || ""),
|
|
424
|
+
Number(e.min_level ?? 1), Number(e.weight ?? 1), String(e.note || ""), (0, text_1.nowIso)(),
|
|
425
425
|
]);
|
|
426
426
|
}
|
|
427
427
|
for (const c of conds) {
|
|
428
428
|
const nid = nodeId.get(String(c.node));
|
|
429
429
|
await mgr.query(`DELETE FROM node_conditions WHERE node_id=? AND name=?`, [nid, String(c.name)]);
|
|
430
|
-
await mgr.query(`INSERT INTO node_conditions(id,node_id,name,kind,expr,op,value,required,effect,weight,note)
|
|
430
|
+
await mgr.query(`INSERT INTO node_conditions(id,node_id,name,kind,expr,op,value,required,effect,weight,note)
|
|
431
431
|
VALUES(?,?,?,?,?,?,?,?,?,?,?)`, [
|
|
432
432
|
(0, text_1.shortId)(["cond", tid, nid, String(c.name)], 10), nid, String(c.name),
|
|
433
433
|
String(c.kind || "flag"), String(c.expr || ""), String(c.op || ">="), String(c.value ?? ""),
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ygl-trees v0.
|
|
1
|
+
# ygl-trees v0.69.0 — 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.69.0",
|
|
4
4
|
"description": "猿光 · 科技树 — database-centric CLI for research → production → knowledge reuse, built for AI agents.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/apps/cli/index.js",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"typeorm": "^1.1.2",
|
|
27
27
|
"@deepseek-ai/cordis": "~4.0.5-alpha.2",
|
|
28
28
|
"@deepseek-ai/cordis-plugin-timer": "~1.1.7-alpha.2",
|
|
29
|
-
"@deepseek-ai/
|
|
30
|
-
"@deepseek-ai/
|
|
29
|
+
"@deepseek-ai/cosmokit": "~1.8.6-alpha.2",
|
|
30
|
+
"@deepseek-ai/schemastery": "~3.18.5-alpha.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^20.0.0",
|