ygl-trees 0.75.1 → 0.76.1

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.
@@ -10,12 +10,13 @@ let node_fs = require("node:fs");
10
10
  node_fs = require_rolldown_runtime.__toESM(node_fs);
11
11
  let node_crypto = require("node:crypto");
12
12
  let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
13
+ require("reflect-metadata");
14
+ let typeorm = require("typeorm");
15
+ let node_events = require("node:events");
13
16
  let _deepseek_ai_cordis_plugin_timer = require("@deepseek-ai/cordis-plugin-timer");
14
17
  _deepseek_ai_cordis_plugin_timer = require_rolldown_runtime.__toESM(_deepseek_ai_cordis_plugin_timer);
15
18
  let node_child_process = require("node:child_process");
16
19
  let _deepseek_ai_dsh_atomic_write = require("@deepseek-ai/dsh-atomic-write");
17
- require("reflect-metadata");
18
- let typeorm = require("typeorm");
19
20
  let _deepseek_ai_schemastery = require("@deepseek-ai/schemastery");
20
21
  _deepseek_ai_schemastery = require_rolldown_runtime.__toESM(_deepseek_ai_schemastery);
21
22
  //#region packages/core/text/src/text.ts
@@ -971,409 +972,998 @@ function bool(args, key) {
971
972
  return v === true || v === "true" || v === "1";
972
973
  }
973
974
  //#endregion
974
- //#region packages/core/database/src/plugin.ts
975
- /**
976
- * DatabasePlugin — provides ctx.database (DatabaseService).
977
- * This is the entry point that apps compose via ctx.plugin().
978
- *
979
- * @module @ygl/core-database/plugin
980
- */
981
- var DatabaseService = class extends _deepseek_ai_cordis.Service {
982
- db;
983
- constructor(ctx, dbFile) {
984
- super(ctx, "database");
985
- const lock = process.env.YGL_TEST !== "1" && process.env.YGL_SERVE !== "1";
986
- this.db = new DB(dbFile, { lock });
987
- }
988
- };
989
- function apply$3(ctx, config) {
990
- new DatabaseService(ctx, config.file);
975
+ //#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorateMetadata.js
976
+ function __decorateMetadata(k, v) {
977
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
991
978
  }
979
+ var init_decorateMetadata = require_rolldown_runtime.__esmMin((() => {}));
992
980
  //#endregion
993
- //#region packages/web/app/src/index.ts
994
- /**
995
- * WebAppPlugin — provides ctx.webApp: serves the built React client
996
- * (apps/web/dist, workspace knowledge — never user config) over the
997
- * dashboard server. Pattern follows DSH packages/bundle/web-app: the app
998
- * entry builds with vite, this runtime-glue plugin resolves the dist and
999
- * hands static-file serving to whoever owns the HTTP surface.
1000
- *
1001
- * @module @ygl/web-app
1002
- */
1003
- /** Resolve the built client dist (workspace knowledge). Pure: safe to call
1004
- * from short-lived CLI processes without betting on async plugin timing.
1005
- *
1006
- * Probe by REPO MARKER, not by counting levels from __dirname: under the
1007
- * tsdown single-file build __dirname collapses to dist/bundle, so any
1008
- * fixed-depth relative path misses. Walking up to the repo root (the dir
1009
- * holding ygl.bundle.json) works from every layout — tsc tree, bundle,
1010
- * and a run straight from src/. */
1011
- function resolveDistRoot(from = __dirname) {
1012
- const candidates = [];
1013
- let dir = from;
1014
- for (let i = 0; i < 6; i++, dir = node_path.dirname(dir)) {
1015
- candidates.push(node_path.join(dir, "apps", "web", "dist"));
1016
- if (node_fs.existsSync(node_path.join(dir, "ygl.bundle.json"))) break;
1017
- }
1018
- candidates.push(node_path.resolve(process.cwd(), "apps", "web", "dist"));
1019
- for (const candidate of candidates) if (node_fs.existsSync(node_path.join(candidate, "index.html"))) return candidate;
1020
- return null;
1021
- }
1022
- const MIME = {
1023
- ".html": "text/html; charset=utf-8",
1024
- ".js": "text/javascript; charset=utf-8",
1025
- ".mjs": "text/javascript; charset=utf-8",
1026
- ".css": "text/css; charset=utf-8",
1027
- ".json": "application/json",
1028
- ".svg": "image/svg+xml",
1029
- ".png": "image/png",
1030
- ".ico": "image/x-icon",
1031
- ".woff2": "font/woff2",
1032
- ".map": "application/json"
1033
- };
1034
- /** URL → dist 静态文件(SPA 回落 index.html;路径遍历安全)。纯函数。 */
1035
- function openStatic(urlPath) {
1036
- const root = resolveDistRoot();
1037
- if (!root) return null;
1038
- const clean = urlPath.split("?")[0].replace(/^\/+/, "");
1039
- let file = node_path.resolve(root, clean === "" ? "index.html" : clean);
1040
- if (!file.startsWith(root)) file = node_path.join(root, "index.html");
1041
- if (!node_fs.existsSync(file) || node_fs.statSync(file).isDirectory()) {
1042
- file = node_path.join(root, "index.html");
1043
- if (!node_fs.existsSync(file)) return null;
1044
- }
1045
- const ext = node_path.extname(file).toLowerCase();
1046
- return {
1047
- body: node_fs.readFileSync(file),
1048
- mime: MIME[ext] || "application/octet-stream"
1049
- };
1050
- }
1051
- var WebAppService = class extends _deepseek_ai_cordis.Service {
1052
- constructor(ctx) {
1053
- super(ctx, "webApp");
1054
- }
1055
- /** Whether a built client exists to serve. */
1056
- available() {
1057
- return resolveDistRoot() !== null;
1058
- }
1059
- /** Delegates to the pure resolver — short-lived CLI callers may use the
1060
- * standalone openStatic() without waiting on plugin timing. */
1061
- open(urlPath) {
1062
- return openStatic(urlPath);
1063
- }
1064
- };
1065
- function apply$2(ctx) {
1066
- new WebAppService(ctx);
981
+ //#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorate.js
982
+ function __decorate(decorators, target, key, desc) {
983
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
984
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
985
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
986
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1067
987
  }
988
+ var init_decorate = require_rolldown_runtime.__esmMin((() => {}));
1068
989
  //#endregion
1069
- //#region packages/techtree/eval/src/eval.ts
990
+ //#region packages/research/pipeline/src/entities.ts
1070
991
  /**
1071
- * Decision-model boolean evaluator — spec R8 (docs/spec.md).
1072
- *
1073
- * Speaks the Vercel AI Gateway v4 evaluation-model protocol (TypeSafe jev):
1074
- * deterministic P(true) judgments, 1-2s/call, native CJK, collapses toward
1075
- * the gray band instead of hard-answering on thin evidence. Material:
1076
- * F:/Users/Administrator/Downloads/vercel-ai-gateway-eval-api.md.
992
+ * Research-domain entities — a typed snapshot of schema v5.
1077
993
  *
1078
- * Offline-safe by design: callers map "no token / network failure" to a
1079
- * PENDING gate outcome (never a hard fail) — the evaluator is an
1080
- * enhancement, not a dependency.
994
+ * Contract with the schema authority:
995
+ * - `synchronize` is ALWAYS false; migrations stay on the PRAGMA
996
+ * user_version track (src/db.ts). If SCHEMA_SQL changes, these
997
+ * entities must be updated in the same commit.
998
+ * - Column names are explicit snake_case to match the database exactly.
999
+ * - FTS5 virtual tables and PRAGMA paths are OUT of entity scope; they
1000
+ * stay raw SQL in store.ts (see docs/plan.md TypeORM boundaries).
1081
1001
  */
1082
- /** Normalize a configured api_base: a bare host gets the standard v4
1083
- * evaluation-model path appended; a full endpoint is used as-is.
1084
- * Config priority is composed by the CALLER (cmds layer):
1085
- * config(decision_model_llm_api_base) > this default. */
1086
- function resolveEndpoint(base) {
1087
- const v = String(base || "").trim().replace(/\/+$/, "");
1088
- if (!v) return GATEWAY_URL;
1089
- if (/\/v4\/ai\/evaluation-model$/.test(v)) return v;
1090
- return `${v}/v4/ai/evaluation-model`;
1091
- }
1092
- /** Token resolution: env first, then ~/.vercel.ai-gateway.env (token=...).
1093
- * Set YGL_EVAL_OFFLINE=1 to force the offline path (tests/demos on
1094
- * machines that DO have a token file — keeps them deterministic). */
1095
- function evalToken() {
1096
- if (process.env.YGL_EVAL_OFFLINE) return "";
1097
- if (process.env.AI_GATEWAY_API_KEY) return process.env.AI_GATEWAY_API_KEY;
1098
- try {
1099
- const m = /^token\s*=\s*(\S+)/m.exec(node_fs.readFileSync(node_path.join(node_os.homedir(), ".vercel.ai-gateway.env"), "utf8"));
1100
- if (m) return m[1];
1101
- } catch {}
1102
- return "";
1103
- }
1104
- /** One boolean question about a state. Retries 429/529 with exponential
1105
- * backoff (<=3); 400/401 and malformed answers return without retry;
1106
- * 60s timeout per attempt. `endpoint` defaults to the Vercel gateway —
1107
- * callers pass the config-resolved value (decision_model_llm_api_base
1108
- * has priority over the default). */
1109
- async function evalBoolean(state, question, token, endpoint = GATEWAY_URL) {
1110
- if (!token) return {
1111
- ok: false,
1112
- error: NO_TOKEN
1113
- };
1114
- const r = await postEval(state, { q: {
1115
- type: "boolean",
1116
- instructions: question
1117
- } }, token, endpoint);
1118
- if (!r.ok) return {
1119
- ok: false,
1120
- error: r.error
1121
- };
1122
- const p = r.answers.q?.probability;
1123
- if (typeof p !== "number") return {
1124
- ok: false,
1125
- error: "malformed answer: no boolean probability"
1002
+ var Project, Query, Source, QuerySource, Evidence, Claim, ClaimEvidence, RESEARCH_ENTITIES;
1003
+ var init_entities$1 = require_rolldown_runtime.__esmMin((() => {
1004
+ init_decorateMetadata();
1005
+ init_decorate();
1006
+ Project = class Project {
1007
+ id;
1008
+ name;
1009
+ question;
1010
+ inclusion;
1011
+ exclusion;
1012
+ status;
1013
+ created;
1014
+ updated;
1015
+ queries;
1016
+ claims;
1017
+ sources;
1126
1018
  };
1127
- return {
1128
- ok: true,
1129
- p,
1130
- usage: r.usage
1019
+ __decorate([(0, typeorm.PrimaryColumn)({
1020
+ name: "id",
1021
+ type: "text"
1022
+ }), __decorateMetadata("design:type", String)], Project.prototype, "id", void 0);
1023
+ __decorate([(0, typeorm.Column)({
1024
+ name: "name",
1025
+ type: "text"
1026
+ }), __decorateMetadata("design:type", String)], Project.prototype, "name", void 0);
1027
+ __decorate([(0, typeorm.Column)({
1028
+ name: "question",
1029
+ type: "text"
1030
+ }), __decorateMetadata("design:type", String)], Project.prototype, "question", void 0);
1031
+ __decorate([(0, typeorm.Column)({
1032
+ name: "inclusion",
1033
+ type: "text"
1034
+ }), __decorateMetadata("design:type", String)], Project.prototype, "inclusion", void 0);
1035
+ __decorate([(0, typeorm.Column)({
1036
+ name: "exclusion",
1037
+ type: "text"
1038
+ }), __decorateMetadata("design:type", String)], Project.prototype, "exclusion", void 0);
1039
+ __decorate([(0, typeorm.Column)({
1040
+ name: "status",
1041
+ type: "text"
1042
+ }), __decorateMetadata("design:type", String)], Project.prototype, "status", void 0);
1043
+ __decorate([(0, typeorm.Column)({
1044
+ name: "created",
1045
+ type: "text"
1046
+ }), __decorateMetadata("design:type", String)], Project.prototype, "created", void 0);
1047
+ __decorate([(0, typeorm.Column)({
1048
+ name: "updated",
1049
+ type: "text"
1050
+ }), __decorateMetadata("design:type", String)], Project.prototype, "updated", void 0);
1051
+ __decorate([(0, typeorm.OneToMany)(() => Query, (q) => q.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "queries", void 0);
1052
+ __decorate([(0, typeorm.OneToMany)(() => Claim, (c) => c.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "claims", void 0);
1053
+ __decorate([(0, typeorm.OneToMany)(() => Source, (s) => s.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "sources", void 0);
1054
+ Project = __decorate([(0, typeorm.Entity)({ name: "projects" })], Project);
1055
+ Query = class Query {
1056
+ id;
1057
+ projectId;
1058
+ text;
1059
+ engine;
1060
+ status;
1061
+ note;
1062
+ contentHash;
1063
+ created;
1064
+ project;
1065
+ querySources;
1131
1066
  };
1132
- }
1133
- function parseEvalSpec(value) {
1134
- const s = String(value ?? "").trim();
1135
- if (s.startsWith("{")) try {
1136
- const j = JSON.parse(s);
1137
- if (j && typeof j.q === "string") return j;
1138
- } catch {}
1139
- return { q: s };
1140
- }
1141
- function packEvalChoiceSpec(q, options, answer) {
1142
- return JSON.stringify({
1143
- q,
1144
- options,
1145
- answer
1146
- });
1147
- }
1148
- function packEvalScoreSpec(q, levels, min) {
1149
- return JSON.stringify({
1150
- q,
1151
- levels,
1152
- min
1153
- });
1154
- }
1155
- async function postEval(state, questions, token, endpoint) {
1156
- const url = resolveEndpoint(endpoint);
1157
- const body = JSON.stringify({
1158
- state,
1159
- questions
1160
- });
1161
- for (let attempt = 0;; attempt++) try {
1162
- const res = await fetch(url, {
1163
- method: "POST",
1164
- headers: {
1165
- Authorization: `Bearer ${token}`,
1166
- "Content-Type": "application/json",
1167
- "ai-model-id": MODEL_ID,
1168
- "ai-evaluation-model-specification-version": "4",
1169
- "ai-gateway-protocol-version": "0.0.1",
1170
- "ai-gateway-auth-method": "api-key"
1171
- },
1172
- body,
1173
- signal: AbortSignal.timeout(6e4)
1174
- });
1175
- if (res.status === 429 || res.status === 529) {
1176
- if (attempt >= 3) return {
1177
- ok: false,
1178
- error: `HTTP ${res.status} after ${attempt + 1} attempts`
1179
- };
1180
- await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
1181
- continue;
1182
- }
1183
- if (!res.ok) return {
1184
- ok: false,
1185
- error: `HTTP ${res.status} (protocol/auth error — no retry)`
1186
- };
1187
- const j = await res.json();
1188
- if (!j?.answers) return {
1189
- ok: false,
1190
- error: "malformed response: no answers"
1191
- };
1192
- return {
1193
- ok: true,
1194
- answers: j.answers,
1195
- usage: j.usage ?? void 0
1196
- };
1197
- } catch (e) {
1198
- const msg = String(e?.message || e);
1199
- if (attempt < 3 && /timeout|abort|network|fetch failed|ECONN/i.test(msg)) {
1200
- await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
1201
- continue;
1202
- }
1203
- return {
1204
- ok: false,
1205
- error: msg
1206
- };
1207
- }
1208
- }
1209
- async function evalChoice(state, question, options, token, endpoint = GATEWAY_URL) {
1210
- if (!token) return {
1211
- ok: false,
1212
- error: NO_TOKEN
1213
- };
1214
- const criteria = {};
1215
- for (const [k, v] of Object.entries(options)) criteria[k] = v || null;
1216
- const r = await postEval(state, { q: {
1217
- type: "choice",
1218
- instructions: question,
1219
- criteria
1220
- } }, token, endpoint);
1221
- if (!r.ok) return {
1222
- ok: false,
1223
- error: r.error
1224
- };
1225
- const a = r.answers.q ?? {};
1226
- if (typeof a.choice !== "string" || !a.probabilities) return {
1227
- ok: false,
1228
- error: "malformed answer: no choice/probabilities"
1067
+ __decorate([(0, typeorm.PrimaryColumn)({
1068
+ name: "id",
1069
+ type: "text"
1070
+ }), __decorateMetadata("design:type", String)], Query.prototype, "id", void 0);
1071
+ __decorate([(0, typeorm.Column)({
1072
+ name: "project_id",
1073
+ type: "text"
1074
+ }), __decorateMetadata("design:type", String)], Query.prototype, "projectId", void 0);
1075
+ __decorate([(0, typeorm.Column)({
1076
+ name: "text",
1077
+ type: "text"
1078
+ }), __decorateMetadata("design:type", String)], Query.prototype, "text", void 0);
1079
+ __decorate([(0, typeorm.Column)({
1080
+ name: "engine",
1081
+ type: "text"
1082
+ }), __decorateMetadata("design:type", String)], Query.prototype, "engine", void 0);
1083
+ __decorate([(0, typeorm.Column)({
1084
+ name: "status",
1085
+ type: "text"
1086
+ }), __decorateMetadata("design:type", String)], Query.prototype, "status", void 0);
1087
+ __decorate([(0, typeorm.Column)({
1088
+ name: "note",
1089
+ type: "text"
1090
+ }), __decorateMetadata("design:type", String)], Query.prototype, "note", void 0);
1091
+ __decorate([(0, typeorm.Column)({
1092
+ name: "content_hash",
1093
+ type: "text"
1094
+ }), __decorateMetadata("design:type", String)], Query.prototype, "contentHash", void 0);
1095
+ __decorate([(0, typeorm.Column)({
1096
+ name: "created",
1097
+ type: "text"
1098
+ }), __decorateMetadata("design:type", String)], Query.prototype, "created", void 0);
1099
+ __decorate([
1100
+ (0, typeorm.ManyToOne)(() => Project, (p) => p.queries, { createForeignKeyConstraints: false }),
1101
+ (0, typeorm.JoinColumn)({ name: "project_id" }),
1102
+ __decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
1103
+ ], Query.prototype, "project", void 0);
1104
+ __decorate([(0, typeorm.OneToMany)(() => QuerySource, (qs) => qs.query, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Query.prototype, "querySources", void 0);
1105
+ Query = __decorate([(0, typeorm.Entity)({ name: "queries" })], Query);
1106
+ Source = class Source {
1107
+ id;
1108
+ projectId;
1109
+ url;
1110
+ title;
1111
+ urlHash;
1112
+ contentHash;
1113
+ abstract;
1114
+ content;
1115
+ engine;
1116
+ relevance;
1117
+ confidence;
1118
+ tags;
1119
+ retrievedAt;
1120
+ created;
1121
+ project;
1122
+ querySources;
1229
1123
  };
1230
- return {
1231
- ok: true,
1232
- choice: a.choice,
1233
- probabilities: a.probabilities,
1234
- usage: r.usage
1124
+ __decorate([(0, typeorm.PrimaryColumn)({
1125
+ name: "id",
1126
+ type: "text"
1127
+ }), __decorateMetadata("design:type", String)], Source.prototype, "id", void 0);
1128
+ __decorate([(0, typeorm.Column)({
1129
+ name: "project_id",
1130
+ type: "text"
1131
+ }), __decorateMetadata("design:type", String)], Source.prototype, "projectId", void 0);
1132
+ __decorate([(0, typeorm.Column)({
1133
+ name: "url",
1134
+ type: "text"
1135
+ }), __decorateMetadata("design:type", String)], Source.prototype, "url", void 0);
1136
+ __decorate([(0, typeorm.Column)({
1137
+ name: "title",
1138
+ type: "text"
1139
+ }), __decorateMetadata("design:type", String)], Source.prototype, "title", void 0);
1140
+ __decorate([(0, typeorm.Column)({
1141
+ name: "url_hash",
1142
+ type: "text"
1143
+ }), __decorateMetadata("design:type", String)], Source.prototype, "urlHash", void 0);
1144
+ __decorate([(0, typeorm.Column)({
1145
+ name: "content_hash",
1146
+ type: "text"
1147
+ }), __decorateMetadata("design:type", String)], Source.prototype, "contentHash", void 0);
1148
+ __decorate([(0, typeorm.Column)({
1149
+ name: "abstract",
1150
+ type: "text"
1151
+ }), __decorateMetadata("design:type", String)], Source.prototype, "abstract", void 0);
1152
+ __decorate([(0, typeorm.Column)({
1153
+ name: "content",
1154
+ type: "text"
1155
+ }), __decorateMetadata("design:type", String)], Source.prototype, "content", void 0);
1156
+ __decorate([(0, typeorm.Column)({
1157
+ name: "engine",
1158
+ type: "text"
1159
+ }), __decorateMetadata("design:type", String)], Source.prototype, "engine", void 0);
1160
+ __decorate([(0, typeorm.Column)({
1161
+ name: "relevance",
1162
+ type: "integer"
1163
+ }), __decorateMetadata("design:type", Number)], Source.prototype, "relevance", void 0);
1164
+ __decorate([(0, typeorm.Column)({
1165
+ name: "confidence",
1166
+ type: "text"
1167
+ }), __decorateMetadata("design:type", String)], Source.prototype, "confidence", void 0);
1168
+ __decorate([(0, typeorm.Column)({
1169
+ name: "tags",
1170
+ type: "text"
1171
+ }), __decorateMetadata("design:type", String)], Source.prototype, "tags", void 0);
1172
+ __decorate([(0, typeorm.Column)({
1173
+ name: "retrieved_at",
1174
+ type: "text"
1175
+ }), __decorateMetadata("design:type", String)], Source.prototype, "retrievedAt", void 0);
1176
+ __decorate([(0, typeorm.Column)({
1177
+ name: "created",
1178
+ type: "text",
1179
+ nullable: true
1180
+ }), __decorateMetadata("design:type", String)], Source.prototype, "created", void 0);
1181
+ __decorate([
1182
+ (0, typeorm.ManyToOne)(() => Project, (p) => p.sources, { createForeignKeyConstraints: false }),
1183
+ (0, typeorm.JoinColumn)({ name: "project_id" }),
1184
+ __decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
1185
+ ], Source.prototype, "project", void 0);
1186
+ __decorate([(0, typeorm.OneToMany)(() => QuerySource, (qs) => qs.source, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Source.prototype, "querySources", void 0);
1187
+ Source = __decorate([(0, typeorm.Entity)({ name: "sources" })], Source);
1188
+ QuerySource = class QuerySource {
1189
+ queryId;
1190
+ sourceId;
1191
+ note;
1192
+ created;
1193
+ query;
1194
+ source;
1235
1195
  };
1236
- }
1237
- /** Tri-state on the EXPECTED answer's probability — same thresholds and
1238
- * gray-band philosophy as boolean P(true). */
1239
- function mapChoiceOutcome(r, answer, high = .7, low = .4) {
1240
- if (!r.ok || !r.probabilities) return {
1241
- outcome: "pending",
1242
- reason: r.error || "no probabilities"
1196
+ __decorate([(0, typeorm.PrimaryColumn)({
1197
+ name: "query_id",
1198
+ type: "text"
1199
+ }), __decorateMetadata("design:type", String)], QuerySource.prototype, "queryId", void 0);
1200
+ __decorate([(0, typeorm.PrimaryColumn)({
1201
+ name: "source_id",
1202
+ type: "text"
1203
+ }), __decorateMetadata("design:type", String)], QuerySource.prototype, "sourceId", void 0);
1204
+ __decorate([(0, typeorm.Column)({
1205
+ name: "note",
1206
+ type: "text"
1207
+ }), __decorateMetadata("design:type", String)], QuerySource.prototype, "note", void 0);
1208
+ __decorate([(0, typeorm.Column)({
1209
+ name: "created",
1210
+ type: "text"
1211
+ }), __decorateMetadata("design:type", String)], QuerySource.prototype, "created", void 0);
1212
+ __decorate([
1213
+ (0, typeorm.ManyToOne)(() => Query, (q) => q.querySources, { createForeignKeyConstraints: false }),
1214
+ (0, typeorm.JoinColumn)({ name: "query_id" }),
1215
+ __decorateMetadata("design:type", typeof Query === "undefined" ? Object : Query)
1216
+ ], QuerySource.prototype, "query", void 0);
1217
+ __decorate([
1218
+ (0, typeorm.ManyToOne)(() => Source, (s) => s.querySources, { createForeignKeyConstraints: false }),
1219
+ (0, typeorm.JoinColumn)({ name: "source_id" }),
1220
+ __decorateMetadata("design:type", typeof Source === "undefined" ? Object : Source)
1221
+ ], QuerySource.prototype, "source", void 0);
1222
+ QuerySource = __decorate([(0, typeorm.Entity)({ name: "query_sources" })], QuerySource);
1223
+ Evidence = class Evidence {
1224
+ id;
1225
+ projectId;
1226
+ sourceId;
1227
+ quote;
1228
+ note;
1229
+ locator;
1230
+ contentHash;
1231
+ created;
1232
+ project;
1233
+ source;
1234
+ claimEvidence;
1243
1235
  };
1244
- const p = r.probabilities[answer];
1245
- if (typeof p !== "number") return {
1246
- outcome: "pending",
1247
- reason: `expected answer '${answer}' not in distribution`
1248
- };
1249
- const base = {
1250
- p,
1251
- choice: r.choice
1236
+ __decorate([(0, typeorm.PrimaryColumn)({
1237
+ name: "id",
1238
+ type: "text"
1239
+ }), __decorateMetadata("design:type", String)], Evidence.prototype, "id", void 0);
1240
+ __decorate([(0, typeorm.Column)({
1241
+ name: "project_id",
1242
+ type: "text"
1243
+ }), __decorateMetadata("design:type", String)], Evidence.prototype, "projectId", void 0);
1244
+ __decorate([(0, typeorm.Column)({
1245
+ name: "source_id",
1246
+ type: "text"
1247
+ }), __decorateMetadata("design:type", String)], Evidence.prototype, "sourceId", void 0);
1248
+ __decorate([(0, typeorm.Column)({
1249
+ name: "quote",
1250
+ type: "text"
1251
+ }), __decorateMetadata("design:type", String)], Evidence.prototype, "quote", void 0);
1252
+ __decorate([(0, typeorm.Column)({
1253
+ name: "note",
1254
+ type: "text"
1255
+ }), __decorateMetadata("design:type", String)], Evidence.prototype, "note", void 0);
1256
+ __decorate([(0, typeorm.Column)({
1257
+ name: "locator",
1258
+ type: "text"
1259
+ }), __decorateMetadata("design:type", String)], Evidence.prototype, "locator", void 0);
1260
+ __decorate([(0, typeorm.Column)({
1261
+ name: "content_hash",
1262
+ type: "text"
1263
+ }), __decorateMetadata("design:type", String)], Evidence.prototype, "contentHash", void 0);
1264
+ __decorate([(0, typeorm.Column)({
1265
+ name: "created",
1266
+ type: "text"
1267
+ }), __decorateMetadata("design:type", String)], Evidence.prototype, "created", void 0);
1268
+ __decorate([
1269
+ (0, typeorm.ManyToOne)(() => Project, void 0, { createForeignKeyConstraints: false }),
1270
+ (0, typeorm.JoinColumn)({ name: "project_id" }),
1271
+ __decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
1272
+ ], Evidence.prototype, "project", void 0);
1273
+ __decorate([
1274
+ (0, typeorm.ManyToOne)(() => Source, void 0, { createForeignKeyConstraints: false }),
1275
+ (0, typeorm.JoinColumn)({ name: "source_id" }),
1276
+ __decorateMetadata("design:type", typeof Source === "undefined" ? Object : Source)
1277
+ ], Evidence.prototype, "source", void 0);
1278
+ __decorate([(0, typeorm.OneToMany)(() => ClaimEvidence, (ce) => ce.evidence, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Evidence.prototype, "claimEvidence", void 0);
1279
+ Evidence = __decorate([(0, typeorm.Entity)({ name: "evidence" })], Evidence);
1280
+ Claim = class Claim {
1281
+ id;
1282
+ projectId;
1283
+ text;
1284
+ status;
1285
+ confidence;
1286
+ contentHash;
1287
+ created;
1288
+ updated;
1289
+ project;
1290
+ claimEvidence;
1252
1291
  };
1253
- if (p >= high) return {
1254
- outcome: "pass",
1255
- ...base
1292
+ __decorate([(0, typeorm.PrimaryColumn)({
1293
+ name: "id",
1294
+ type: "text"
1295
+ }), __decorateMetadata("design:type", String)], Claim.prototype, "id", void 0);
1296
+ __decorate([(0, typeorm.Column)({
1297
+ name: "project_id",
1298
+ type: "text"
1299
+ }), __decorateMetadata("design:type", String)], Claim.prototype, "projectId", void 0);
1300
+ __decorate([(0, typeorm.Column)({
1301
+ name: "text",
1302
+ type: "text"
1303
+ }), __decorateMetadata("design:type", String)], Claim.prototype, "text", void 0);
1304
+ __decorate([(0, typeorm.Column)({
1305
+ name: "status",
1306
+ type: "text"
1307
+ }), __decorateMetadata("design:type", String)], Claim.prototype, "status", void 0);
1308
+ __decorate([(0, typeorm.Column)({
1309
+ name: "confidence",
1310
+ type: "text"
1311
+ }), __decorateMetadata("design:type", String)], Claim.prototype, "confidence", void 0);
1312
+ __decorate([(0, typeorm.Column)({
1313
+ name: "content_hash",
1314
+ type: "text"
1315
+ }), __decorateMetadata("design:type", String)], Claim.prototype, "contentHash", void 0);
1316
+ __decorate([(0, typeorm.Column)({
1317
+ name: "created",
1318
+ type: "text"
1319
+ }), __decorateMetadata("design:type", String)], Claim.prototype, "created", void 0);
1320
+ __decorate([(0, typeorm.Column)({
1321
+ name: "updated",
1322
+ type: "text",
1323
+ nullable: true
1324
+ }), __decorateMetadata("design:type", String)], Claim.prototype, "updated", void 0);
1325
+ __decorate([
1326
+ (0, typeorm.ManyToOne)(() => Project, (p) => p.claims, { createForeignKeyConstraints: false }),
1327
+ (0, typeorm.JoinColumn)({ name: "project_id" }),
1328
+ __decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
1329
+ ], Claim.prototype, "project", void 0);
1330
+ __decorate([(0, typeorm.OneToMany)(() => ClaimEvidence, (ce) => ce.claim, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Claim.prototype, "claimEvidence", void 0);
1331
+ Claim = __decorate([(0, typeorm.Entity)({ name: "claims" })], Claim);
1332
+ ClaimEvidence = class ClaimEvidence {
1333
+ claimId;
1334
+ evidenceId;
1335
+ stance;
1336
+ claim;
1337
+ evidence;
1256
1338
  };
1257
- if (p <= low) return {
1258
- outcome: "fail",
1259
- ...base
1339
+ __decorate([(0, typeorm.PrimaryColumn)({
1340
+ name: "claim_id",
1341
+ type: "text"
1342
+ }), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "claimId", void 0);
1343
+ __decorate([(0, typeorm.PrimaryColumn)({
1344
+ name: "evidence_id",
1345
+ type: "text"
1346
+ }), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "evidenceId", void 0);
1347
+ __decorate([(0, typeorm.Column)({
1348
+ name: "stance",
1349
+ type: "text"
1350
+ }), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "stance", void 0);
1351
+ __decorate([
1352
+ (0, typeorm.ManyToOne)(() => Claim, (c) => c.claimEvidence, { createForeignKeyConstraints: false }),
1353
+ (0, typeorm.JoinColumn)({ name: "claim_id" }),
1354
+ __decorateMetadata("design:type", typeof Claim === "undefined" ? Object : Claim)
1355
+ ], ClaimEvidence.prototype, "claim", void 0);
1356
+ __decorate([
1357
+ (0, typeorm.ManyToOne)(() => Evidence, void 0, { createForeignKeyConstraints: false }),
1358
+ (0, typeorm.JoinColumn)({ name: "evidence_id" }),
1359
+ __decorateMetadata("design:type", typeof Evidence === "undefined" ? Object : Evidence)
1360
+ ], ClaimEvidence.prototype, "evidence", void 0);
1361
+ ClaimEvidence = __decorate([(0, typeorm.Entity)({ name: "claim_evidence" })], ClaimEvidence);
1362
+ RESEARCH_ENTITIES = [
1363
+ Project,
1364
+ Query,
1365
+ Source,
1366
+ QuerySource,
1367
+ Evidence,
1368
+ Claim,
1369
+ ClaimEvidence
1370
+ ];
1371
+ }));
1372
+ //#endregion
1373
+ //#region packages/core/database/src/entities.ts
1374
+ /** Cross-cutting entities (audit trail). Research-domain entities live in
1375
+ * research.ts; both are registered in src/orm.ts. */
1376
+ var Event, CORE_ENTITIES;
1377
+ var init_entities = require_rolldown_runtime.__esmMin((() => {
1378
+ init_decorateMetadata();
1379
+ init_decorate();
1380
+ Event = class Event {
1381
+ id;
1382
+ ts;
1383
+ actor;
1384
+ kind;
1385
+ entity;
1386
+ entityId;
1387
+ payload;
1388
+ /** v7 telemetry severity: info | warn | error (spec R10.2) */
1389
+ level;
1260
1390
  };
1261
- return {
1262
- outcome: "pending",
1263
- ...base,
1264
- reason: `gray band ${low}<P<${high} (process state)`
1391
+ __decorate([(0, typeorm.PrimaryColumn)({
1392
+ name: "id",
1393
+ type: "text"
1394
+ }), __decorateMetadata("design:type", String)], Event.prototype, "id", void 0);
1395
+ __decorate([(0, typeorm.Column)({
1396
+ name: "ts",
1397
+ type: "text"
1398
+ }), __decorateMetadata("design:type", String)], Event.prototype, "ts", void 0);
1399
+ __decorate([(0, typeorm.Column)({
1400
+ name: "actor",
1401
+ type: "text"
1402
+ }), __decorateMetadata("design:type", String)], Event.prototype, "actor", void 0);
1403
+ __decorate([(0, typeorm.Column)({
1404
+ name: "kind",
1405
+ type: "text"
1406
+ }), __decorateMetadata("design:type", String)], Event.prototype, "kind", void 0);
1407
+ __decorate([(0, typeorm.Column)({
1408
+ name: "entity",
1409
+ type: "text"
1410
+ }), __decorateMetadata("design:type", String)], Event.prototype, "entity", void 0);
1411
+ __decorate([(0, typeorm.Column)({
1412
+ name: "entity_id",
1413
+ type: "text"
1414
+ }), __decorateMetadata("design:type", String)], Event.prototype, "entityId", void 0);
1415
+ __decorate([(0, typeorm.Column)({
1416
+ name: "payload",
1417
+ type: "text"
1418
+ }), __decorateMetadata("design:type", String)], Event.prototype, "payload", void 0);
1419
+ __decorate([(0, typeorm.Column)({
1420
+ name: "level",
1421
+ type: "text",
1422
+ default: "info"
1423
+ }), __decorateMetadata("design:type", String)], Event.prototype, "level", void 0);
1424
+ Event = __decorate([(0, typeorm.Entity)({ name: "events" })], Event);
1425
+ CORE_ENTITIES = [Event];
1426
+ }));
1427
+ //#endregion
1428
+ //#region packages/core/database/src/orm.ts
1429
+ /**
1430
+ * Lazy TypeORM DataSource over the SAME SQLite file the CLI works on.
1431
+ *
1432
+ * Boundaries (see docs/plan.md "TypeORM boundaries"):
1433
+ * - `synchronize` is permanently false — schema authority stays with
1434
+ * SCHEMA_SQL + PRAGMA user_version migrations in src/db.ts. Entities in
1435
+ * src/entities/ are a typed snapshot; change them in the same commit as
1436
+ * SCHEMA_SQL.
1437
+ * - TypeORM is a QUERY layer here, not a migration layer. FTS5 MATCH /
1438
+ * bm25 loops, PRAGMA paths and the PG sync/migrate machinery stay raw
1439
+ * SQL on the DB class.
1440
+ * - CLI commands using the ORM must return a Promise (main() awaits) so
1441
+ * the DataSource settles before the wasm DB closes.
1442
+ */
1443
+ var orm_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
1444
+ closeOrm: () => closeOrm,
1445
+ getOrm: () => getOrm
1446
+ });
1447
+ async function getOrm(dbFile) {
1448
+ if (cached && cached.file === dbFile) return cached.ds;
1449
+ const ds = new typeorm.DataSource({
1450
+ type: "better-sqlite3",
1451
+ database: dbFile,
1452
+ synchronize: false,
1453
+ entities: [...RESEARCH_ENTITIES, ...CORE_ENTITIES]
1454
+ });
1455
+ await ds.initialize();
1456
+ await ds.query("PRAGMA synchronous=NORMAL");
1457
+ cached = {
1458
+ file: dbFile,
1459
+ ds
1265
1460
  };
1461
+ return ds;
1266
1462
  }
1267
- async function evalScore(state, question, levels, token, endpoint = GATEWAY_URL) {
1268
- if (!token) return {
1269
- ok: false,
1270
- error: NO_TOKEN
1271
- };
1272
- const r = await postEval(state, { q: {
1273
- type: "score",
1274
- instructions: question,
1275
- criteria: levels
1276
- } }, token, endpoint);
1277
- if (!r.ok) return {
1278
- ok: false,
1279
- error: r.error
1280
- };
1281
- const a = r.answers.q ?? {};
1282
- if (typeof a.score !== "number" || !a.probabilities) return {
1283
- ok: false,
1284
- error: "malformed answer: no score/probabilities"
1285
- };
1286
- return {
1287
- ok: true,
1288
- score: a.score,
1289
- probabilities: a.probabilities,
1290
- usage: r.usage
1291
- };
1463
+ /** Destroy the cached DataSource (tests/scratch dirs cannot be removed on
1464
+ * Windows while a better-sqlite3 handle keeps the file open — EPERM). */
1465
+ async function closeOrm() {
1466
+ if (cached) {
1467
+ try {
1468
+ await cached.ds.destroy();
1469
+ } catch {}
1470
+ cached = null;
1471
+ }
1292
1472
  }
1293
- /** Tri-state on the ordinal scale: score >= min → pass; score <= min-1 →
1294
- * fail; the ±1 band around the threshold is the gray zone (the score
1295
- * counterpart of boolean's (0.4, 0.7) process state). */
1296
- function mapScoreOutcome(r, min) {
1297
- if (!r.ok || r.score === void 0) return {
1298
- outcome: "pending",
1299
- reason: r.error || "no score"
1300
- };
1301
- const s = r.score;
1302
- if (s >= min) return {
1303
- outcome: "pass",
1304
- score: s
1305
- };
1306
- if (s <= min - 1) return {
1307
- outcome: "fail",
1308
- score: s
1309
- };
1473
+ var cached;
1474
+ var init_orm = require_rolldown_runtime.__esmMin((() => {
1475
+ init_entities$1();
1476
+ init_entities();
1477
+ cached = null;
1478
+ }));
1479
+ //#endregion
1480
+ //#region packages/core/text/src/eventbus.ts
1481
+ /**
1482
+ * Cross-domain event bus — connects logEvent writes to real-time consumers.
1483
+ *
1484
+ * Architecture: a process-wide EventEmitter singleton. Write services call
1485
+ * `emitEvent()` after logEvent persists to the events table; consumers
1486
+ * (webui SSE, user plugins, future watchers) subscribe with `onEvent()`.
1487
+ *
1488
+ * This makes "ctx.on reactivity" real without requiring a full Cordis
1489
+ * event system — a lightweight bridge that the webui and plugins can use.
1490
+ *
1491
+ * @module @ygl/core-text/eventbus
1492
+ */
1493
+ var eventbus_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
1494
+ emitEvent: () => emitEvent,
1495
+ onEvent: () => onEvent,
1496
+ onEventKind: () => onEventKind
1497
+ });
1498
+ /** Publish a cross-domain event (called after events table write). */
1499
+ function emitEvent(evt) {
1500
+ bus.emit("ygl", evt);
1501
+ bus.emit(`ygl:${evt.kind}`, evt);
1502
+ }
1503
+ /** Subscribe to all events. Returns an unsubscribe function. */
1504
+ function onEvent(handler) {
1505
+ bus.on("ygl", handler);
1506
+ return () => bus.off("ygl", handler);
1507
+ }
1508
+ /** Subscribe to a specific kind (e.g. "node.unlock"). Returns unsubscribe. */
1509
+ function onEventKind(kind, handler) {
1510
+ bus.on(`ygl:${kind}`, handler);
1511
+ return () => bus.off(`ygl:${kind}`, handler);
1512
+ }
1513
+ var bus;
1514
+ var init_eventbus = require_rolldown_runtime.__esmMin((() => {
1515
+ bus = new node_events.EventEmitter();
1516
+ bus.setMaxListeners(50);
1517
+ }));
1518
+ //#endregion
1519
+ //#region packages/core/database/src/plugin.ts
1520
+ /**
1521
+ * DatabasePlugin — provides ctx.database (DatabaseService).
1522
+ * This is the entry point that apps compose via ctx.plugin().
1523
+ *
1524
+ * @module @ygl/core-database/plugin
1525
+ */
1526
+ var DatabaseService = class extends _deepseek_ai_cordis.Service {
1527
+ db;
1528
+ disposed = false;
1529
+ constructor(ctx, dbFile) {
1530
+ super(ctx, "database");
1531
+ const lock = process.env.YGL_TEST !== "1" && process.env.YGL_SERVE !== "1";
1532
+ this.db = new DB(dbFile, { lock });
1533
+ }
1534
+ /** Lifecycle: Cordis calls this when the Context is disposed — the
1535
+ * "Services stop with Context, resources auto-clean" promise made real.
1536
+ * Closes the SQLite handle AND the cached ORM DataSource. */
1537
+ stop() {
1538
+ if (this.disposed) return;
1539
+ this.disposed = true;
1540
+ try {
1541
+ const { closeOrm } = (init_orm(), require_rolldown_runtime.__toCommonJS(orm_exports));
1542
+ closeOrm().catch(() => {});
1543
+ } catch {}
1544
+ try {
1545
+ this.db.close();
1546
+ } catch {}
1547
+ }
1548
+ };
1549
+ function apply$3(ctx, config) {
1550
+ new DatabaseService(ctx, config.file);
1551
+ try {
1552
+ const { onEvent, onEventKind } = (init_eventbus(), require_rolldown_runtime.__toCommonJS(eventbus_exports));
1553
+ const kindSubs = [onEvent((evt) => {
1554
+ try {
1555
+ ctx.emit("ygl", evt);
1556
+ } catch {}
1557
+ })];
1558
+ for (const kind of [
1559
+ "node.unlock",
1560
+ "node.lock",
1561
+ "node.add",
1562
+ "node.link",
1563
+ "kb.add",
1564
+ "kb.rm",
1565
+ "claim.resolve",
1566
+ "claim.add",
1567
+ "task.status",
1568
+ "project.new",
1569
+ "skill.add"
1570
+ ]) kindSubs.push(onEventKind(kind, (evt) => {
1571
+ try {
1572
+ ctx.emit(`ygl:${kind}`, evt);
1573
+ } catch {}
1574
+ }));
1575
+ ctx.fiber?.collect?.(() => {
1576
+ for (const u of kindSubs) try {
1577
+ u();
1578
+ } catch {}
1579
+ });
1580
+ } catch {}
1581
+ }
1582
+ //#endregion
1583
+ //#region packages/web/app/src/index.ts
1584
+ /**
1585
+ * WebAppPlugin — provides ctx.webApp: serves the built React client
1586
+ * (apps/web/dist, workspace knowledge — never user config) over the
1587
+ * dashboard server. Pattern follows DSH packages/bundle/web-app: the app
1588
+ * entry builds with vite, this runtime-glue plugin resolves the dist and
1589
+ * hands static-file serving to whoever owns the HTTP surface.
1590
+ *
1591
+ * @module @ygl/web-app
1592
+ */
1593
+ /** Resolve the built client dist (workspace knowledge). Pure: safe to call
1594
+ * from short-lived CLI processes without betting on async plugin timing.
1595
+ *
1596
+ * Probe by REPO MARKER, not by counting levels from __dirname: under the
1597
+ * tsdown single-file build __dirname collapses to dist/bundle, so any
1598
+ * fixed-depth relative path misses. Walking up to the repo root (the dir
1599
+ * holding ygl.bundle.json) works from every layout — tsc tree, bundle,
1600
+ * and a run straight from src/. */
1601
+ function resolveDistRoot(from = __dirname) {
1602
+ const candidates = [];
1603
+ let dir = from;
1604
+ for (let i = 0; i < 6; i++, dir = node_path.dirname(dir)) {
1605
+ candidates.push(node_path.join(dir, "apps", "web", "dist"));
1606
+ if (node_fs.existsSync(node_path.join(dir, "ygl.bundle.json"))) break;
1607
+ }
1608
+ candidates.push(node_path.resolve(process.cwd(), "apps", "web", "dist"));
1609
+ for (const candidate of candidates) if (node_fs.existsSync(node_path.join(candidate, "index.html"))) return candidate;
1610
+ return null;
1611
+ }
1612
+ const MIME = {
1613
+ ".html": "text/html; charset=utf-8",
1614
+ ".js": "text/javascript; charset=utf-8",
1615
+ ".mjs": "text/javascript; charset=utf-8",
1616
+ ".css": "text/css; charset=utf-8",
1617
+ ".json": "application/json",
1618
+ ".svg": "image/svg+xml",
1619
+ ".png": "image/png",
1620
+ ".ico": "image/x-icon",
1621
+ ".woff2": "font/woff2",
1622
+ ".map": "application/json"
1623
+ };
1624
+ /** URL → dist 静态文件(SPA 回落 index.html;路径遍历安全)。纯函数。 */
1625
+ function openStatic(urlPath) {
1626
+ const root = resolveDistRoot();
1627
+ if (!root) return null;
1628
+ const clean = urlPath.split("?")[0].replace(/^\/+/, "");
1629
+ let file = node_path.resolve(root, clean === "" ? "index.html" : clean);
1630
+ if (!file.startsWith(root)) file = node_path.join(root, "index.html");
1631
+ if (!node_fs.existsSync(file) || node_fs.statSync(file).isDirectory()) {
1632
+ file = node_path.join(root, "index.html");
1633
+ if (!node_fs.existsSync(file)) return null;
1634
+ }
1635
+ const ext = node_path.extname(file).toLowerCase();
1310
1636
  return {
1311
- outcome: "pending",
1312
- score: s,
1313
- reason: `gray band [${min - 1}, ${min}) around expected level ${min}`
1637
+ body: node_fs.readFileSync(file),
1638
+ mime: MIME[ext] || "application/octet-stream"
1314
1639
  };
1315
1640
  }
1316
- async function evalMixed(state, questions, token, endpoint = GATEWAY_URL) {
1317
- if (!questions.length) return {
1318
- ok: true,
1319
- results: []
1320
- };
1641
+ var WebAppService = class extends _deepseek_ai_cordis.Service {
1642
+ constructor(ctx) {
1643
+ super(ctx, "webApp");
1644
+ }
1645
+ /** Whether a built client exists to serve. */
1646
+ available() {
1647
+ return resolveDistRoot() !== null;
1648
+ }
1649
+ /** Delegates to the pure resolver — short-lived CLI callers may use the
1650
+ * standalone openStatic() without waiting on plugin timing. */
1651
+ open(urlPath) {
1652
+ return openStatic(urlPath);
1653
+ }
1654
+ };
1655
+ function apply$2(ctx) {
1656
+ new WebAppService(ctx);
1657
+ }
1658
+ //#endregion
1659
+ //#region packages/techtree/eval/src/eval.ts
1660
+ /**
1661
+ * Decision-model boolean evaluator — spec R8 (docs/spec.md).
1662
+ *
1663
+ * Speaks the Vercel AI Gateway v4 evaluation-model protocol (TypeSafe jev):
1664
+ * deterministic P(true) judgments, 1-2s/call, native CJK, collapses toward
1665
+ * the gray band instead of hard-answering on thin evidence. Material:
1666
+ * F:/Users/Administrator/Downloads/vercel-ai-gateway-eval-api.md.
1667
+ *
1668
+ * Offline-safe by design: callers map "no token / network failure" to a
1669
+ * PENDING gate outcome (never a hard fail) — the evaluator is an
1670
+ * enhancement, not a dependency.
1671
+ */
1672
+ /** Normalize a configured api_base: a bare host gets the standard v4
1673
+ * evaluation-model path appended; a full endpoint is used as-is.
1674
+ * Config priority is composed by the CALLER (cmds layer):
1675
+ * config(decision_model_llm_api_base) > this default. */
1676
+ function resolveEndpoint(base) {
1677
+ const v = String(base || "").trim().replace(/\/+$/, "");
1678
+ if (!v) return GATEWAY_URL;
1679
+ if (/\/v4\/ai\/evaluation-model$/.test(v)) return v;
1680
+ return `${v}/v4/ai/evaluation-model`;
1681
+ }
1682
+ /** Token resolution: env first, then ~/.vercel.ai-gateway.env (token=...).
1683
+ * Set YGL_EVAL_OFFLINE=1 to force the offline path (tests/demos on
1684
+ * machines that DO have a token file — keeps them deterministic). */
1685
+ function evalToken() {
1686
+ if (process.env.YGL_EVAL_OFFLINE) return "";
1687
+ if (process.env.AI_GATEWAY_API_KEY) return process.env.AI_GATEWAY_API_KEY;
1688
+ try {
1689
+ const m = /^token\s*=\s*(\S+)/m.exec(node_fs.readFileSync(node_path.join(node_os.homedir(), ".vercel.ai-gateway.env"), "utf8"));
1690
+ if (m) return m[1];
1691
+ } catch {}
1692
+ return "";
1693
+ }
1694
+ /** One boolean question about a state. Retries 429/529 with exponential
1695
+ * backoff (<=3); 400/401 and malformed answers return without retry;
1696
+ * 60s timeout per attempt. `endpoint` defaults to the Vercel gateway —
1697
+ * callers pass the config-resolved value (decision_model_llm_api_base
1698
+ * has priority over the default). */
1699
+ async function evalBoolean(state, question, token, endpoint = GATEWAY_URL) {
1321
1700
  if (!token) return {
1322
1701
  ok: false,
1323
1702
  error: NO_TOKEN
1324
1703
  };
1325
- const qmap = {};
1326
- questions.forEach((q, i) => {
1327
- if (q.type === "boolean") qmap[`q${i}`] = {
1328
- type: "boolean",
1329
- instructions: q.question
1330
- };
1331
- else if (q.type === "choice") {
1332
- const criteria = {};
1333
- for (const [k, v] of Object.entries(q.options)) criteria[k] = v || null;
1334
- qmap[`q${i}`] = {
1335
- type: "choice",
1336
- instructions: q.question,
1337
- criteria
1338
- };
1339
- } else qmap[`q${i}`] = {
1340
- type: "score",
1341
- instructions: q.question,
1342
- criteria: q.levels
1343
- };
1344
- });
1345
- const r = await postEval(state, qmap, token, endpoint);
1704
+ const r = await postEval(state, { q: {
1705
+ type: "boolean",
1706
+ instructions: question
1707
+ } }, token, endpoint);
1346
1708
  if (!r.ok) return {
1347
1709
  ok: false,
1348
1710
  error: r.error
1349
1711
  };
1350
- const results = [];
1351
- for (let i = 0; i < questions.length; i++) {
1352
- const a = r.answers[`q${i}`] ?? {};
1353
- const t = questions[i].type;
1354
- if (t === "boolean") {
1355
- if (typeof a.probability !== "number") return {
1356
- ok: false,
1357
- error: `malformed answer q${i}: no boolean probability`
1358
- };
1359
- results.push({
1360
- ok: true,
1361
- type: "boolean",
1362
- p: a.probability
1363
- });
1364
- } else if (t === "choice") {
1365
- if (typeof a.choice !== "string" || !a.probabilities) return {
1366
- ok: false,
1367
- error: `malformed answer q${i}: no choice/probabilities`
1368
- };
1369
- results.push({
1370
- ok: true,
1371
- type: "choice",
1372
- choice: a.choice,
1373
- probabilities: a.probabilities
1374
- });
1375
- } else {
1376
- if (typeof a.score !== "number" || !a.probabilities) return {
1712
+ const p = r.answers.q?.probability;
1713
+ if (typeof p !== "number") return {
1714
+ ok: false,
1715
+ error: "malformed answer: no boolean probability"
1716
+ };
1717
+ return {
1718
+ ok: true,
1719
+ p,
1720
+ usage: r.usage
1721
+ };
1722
+ }
1723
+ function parseEvalSpec(value) {
1724
+ const s = String(value ?? "").trim();
1725
+ if (s.startsWith("{")) try {
1726
+ const j = JSON.parse(s);
1727
+ if (j && typeof j.q === "string") return j;
1728
+ } catch {}
1729
+ return { q: s };
1730
+ }
1731
+ function packEvalChoiceSpec(q, options, answer) {
1732
+ return JSON.stringify({
1733
+ q,
1734
+ options,
1735
+ answer
1736
+ });
1737
+ }
1738
+ function packEvalScoreSpec(q, levels, min) {
1739
+ return JSON.stringify({
1740
+ q,
1741
+ levels,
1742
+ min
1743
+ });
1744
+ }
1745
+ async function postEval(state, questions, token, endpoint) {
1746
+ const url = resolveEndpoint(endpoint);
1747
+ const body = JSON.stringify({
1748
+ state,
1749
+ questions
1750
+ });
1751
+ for (let attempt = 0;; attempt++) try {
1752
+ const res = await fetch(url, {
1753
+ method: "POST",
1754
+ headers: {
1755
+ Authorization: `Bearer ${token}`,
1756
+ "Content-Type": "application/json",
1757
+ "ai-model-id": MODEL_ID,
1758
+ "ai-evaluation-model-specification-version": "4",
1759
+ "ai-gateway-protocol-version": "0.0.1",
1760
+ "ai-gateway-auth-method": "api-key"
1761
+ },
1762
+ body,
1763
+ signal: AbortSignal.timeout(6e4)
1764
+ });
1765
+ if (res.status === 429 || res.status === 529) {
1766
+ if (attempt >= 3) return {
1767
+ ok: false,
1768
+ error: `HTTP ${res.status} after ${attempt + 1} attempts`
1769
+ };
1770
+ await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
1771
+ continue;
1772
+ }
1773
+ if (!res.ok) return {
1774
+ ok: false,
1775
+ error: `HTTP ${res.status} (protocol/auth error — no retry)`
1776
+ };
1777
+ const j = await res.json();
1778
+ if (!j?.answers) return {
1779
+ ok: false,
1780
+ error: "malformed response: no answers"
1781
+ };
1782
+ return {
1783
+ ok: true,
1784
+ answers: j.answers,
1785
+ usage: j.usage ?? void 0
1786
+ };
1787
+ } catch (e) {
1788
+ const msg = String(e?.message || e);
1789
+ if (attempt < 3 && /timeout|abort|network|fetch failed|ECONN/i.test(msg)) {
1790
+ await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
1791
+ continue;
1792
+ }
1793
+ return {
1794
+ ok: false,
1795
+ error: msg
1796
+ };
1797
+ }
1798
+ }
1799
+ async function evalChoice(state, question, options, token, endpoint = GATEWAY_URL) {
1800
+ if (!token) return {
1801
+ ok: false,
1802
+ error: NO_TOKEN
1803
+ };
1804
+ const criteria = {};
1805
+ for (const [k, v] of Object.entries(options)) criteria[k] = v || null;
1806
+ const r = await postEval(state, { q: {
1807
+ type: "choice",
1808
+ instructions: question,
1809
+ criteria
1810
+ } }, token, endpoint);
1811
+ if (!r.ok) return {
1812
+ ok: false,
1813
+ error: r.error
1814
+ };
1815
+ const a = r.answers.q ?? {};
1816
+ if (typeof a.choice !== "string" || !a.probabilities) return {
1817
+ ok: false,
1818
+ error: "malformed answer: no choice/probabilities"
1819
+ };
1820
+ return {
1821
+ ok: true,
1822
+ choice: a.choice,
1823
+ probabilities: a.probabilities,
1824
+ usage: r.usage
1825
+ };
1826
+ }
1827
+ /** Tri-state on the EXPECTED answer's probability — same thresholds and
1828
+ * gray-band philosophy as boolean P(true). */
1829
+ function mapChoiceOutcome(r, answer, high = .7, low = .4) {
1830
+ if (!r.ok || !r.probabilities) return {
1831
+ outcome: "pending",
1832
+ reason: r.error || "no probabilities"
1833
+ };
1834
+ const p = r.probabilities[answer];
1835
+ if (typeof p !== "number") return {
1836
+ outcome: "pending",
1837
+ reason: `expected answer '${answer}' not in distribution`
1838
+ };
1839
+ const base = {
1840
+ p,
1841
+ choice: r.choice
1842
+ };
1843
+ if (p >= high) return {
1844
+ outcome: "pass",
1845
+ ...base
1846
+ };
1847
+ if (p <= low) return {
1848
+ outcome: "fail",
1849
+ ...base
1850
+ };
1851
+ return {
1852
+ outcome: "pending",
1853
+ ...base,
1854
+ reason: `gray band ${low}<P<${high} (process state)`
1855
+ };
1856
+ }
1857
+ async function evalScore(state, question, levels, token, endpoint = GATEWAY_URL) {
1858
+ if (!token) return {
1859
+ ok: false,
1860
+ error: NO_TOKEN
1861
+ };
1862
+ const r = await postEval(state, { q: {
1863
+ type: "score",
1864
+ instructions: question,
1865
+ criteria: levels
1866
+ } }, token, endpoint);
1867
+ if (!r.ok) return {
1868
+ ok: false,
1869
+ error: r.error
1870
+ };
1871
+ const a = r.answers.q ?? {};
1872
+ if (typeof a.score !== "number" || !a.probabilities) return {
1873
+ ok: false,
1874
+ error: "malformed answer: no score/probabilities"
1875
+ };
1876
+ return {
1877
+ ok: true,
1878
+ score: a.score,
1879
+ probabilities: a.probabilities,
1880
+ usage: r.usage
1881
+ };
1882
+ }
1883
+ /** Tri-state on the ordinal scale: score >= min → pass; score <= min-1 →
1884
+ * fail; the ±1 band around the threshold is the gray zone (the score
1885
+ * counterpart of boolean's (0.4, 0.7) process state). */
1886
+ function mapScoreOutcome(r, min) {
1887
+ if (!r.ok || r.score === void 0) return {
1888
+ outcome: "pending",
1889
+ reason: r.error || "no score"
1890
+ };
1891
+ const s = r.score;
1892
+ if (s >= min) return {
1893
+ outcome: "pass",
1894
+ score: s
1895
+ };
1896
+ if (s <= min - 1) return {
1897
+ outcome: "fail",
1898
+ score: s
1899
+ };
1900
+ return {
1901
+ outcome: "pending",
1902
+ score: s,
1903
+ reason: `gray band [${min - 1}, ${min}) around expected level ${min}`
1904
+ };
1905
+ }
1906
+ async function evalMixed(state, questions, token, endpoint = GATEWAY_URL) {
1907
+ if (!questions.length) return {
1908
+ ok: true,
1909
+ results: []
1910
+ };
1911
+ if (!token) return {
1912
+ ok: false,
1913
+ error: NO_TOKEN
1914
+ };
1915
+ const qmap = {};
1916
+ questions.forEach((q, i) => {
1917
+ if (q.type === "boolean") qmap[`q${i}`] = {
1918
+ type: "boolean",
1919
+ instructions: q.question
1920
+ };
1921
+ else if (q.type === "choice") {
1922
+ const criteria = {};
1923
+ for (const [k, v] of Object.entries(q.options)) criteria[k] = v || null;
1924
+ qmap[`q${i}`] = {
1925
+ type: "choice",
1926
+ instructions: q.question,
1927
+ criteria
1928
+ };
1929
+ } else qmap[`q${i}`] = {
1930
+ type: "score",
1931
+ instructions: q.question,
1932
+ criteria: q.levels
1933
+ };
1934
+ });
1935
+ const r = await postEval(state, qmap, token, endpoint);
1936
+ if (!r.ok) return {
1937
+ ok: false,
1938
+ error: r.error
1939
+ };
1940
+ const results = [];
1941
+ for (let i = 0; i < questions.length; i++) {
1942
+ const a = r.answers[`q${i}`] ?? {};
1943
+ const t = questions[i].type;
1944
+ if (t === "boolean") {
1945
+ if (typeof a.probability !== "number") return {
1946
+ ok: false,
1947
+ error: `malformed answer q${i}: no boolean probability`
1948
+ };
1949
+ results.push({
1950
+ ok: true,
1951
+ type: "boolean",
1952
+ p: a.probability
1953
+ });
1954
+ } else if (t === "choice") {
1955
+ if (typeof a.choice !== "string" || !a.probabilities) return {
1956
+ ok: false,
1957
+ error: `malformed answer q${i}: no choice/probabilities`
1958
+ };
1959
+ results.push({
1960
+ ok: true,
1961
+ type: "choice",
1962
+ choice: a.choice,
1963
+ probabilities: a.probabilities
1964
+ });
1965
+ } else {
1966
+ if (typeof a.score !== "number" || !a.probabilities) return {
1377
1967
  ok: false,
1378
1968
  error: `malformed answer q${i}: no score/probabilities`
1379
1969
  };
@@ -3273,705 +3863,200 @@ CREATE TABLE claims (
3273
3863
  content_hash text NOT NULL DEFAULT '', created text NOT NULL, UNIQUE (project_id, content_hash));
3274
3864
  CREATE TABLE claim_evidence (
3275
3865
  claim_id text NOT NULL REFERENCES claims(id) ON DELETE CASCADE,
3276
- evidence_id text NOT NULL REFERENCES evidence(id) ON DELETE CASCADE,
3277
- stance text NOT NULL DEFAULT 'supports', PRIMARY KEY (claim_id, evidence_id));
3278
- CREATE TABLE artifacts (
3279
- id text PRIMARY KEY, project_id text NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
3280
- kind text NOT NULL DEFAULT 'section', title text NOT NULL DEFAULT '',
3281
- format text NOT NULL DEFAULT 'markdown', content text NOT NULL DEFAULT '',
3282
- version integer NOT NULL DEFAULT 1, created text NOT NULL,
3283
- meta text NOT NULL DEFAULT '');
3284
- CREATE TABLE kb_notes (
3285
- id text PRIMARY KEY, title text NOT NULL, body text NOT NULL DEFAULT '',
3286
- source_url text DEFAULT '', origin text DEFAULT '', tags text DEFAULT '',
3287
- confidence text NOT NULL DEFAULT 'B', reuse integer NOT NULL DEFAULT 0,
3288
- content_hash text NOT NULL DEFAULT '', created text NOT NULL, updated text NOT NULL,
3289
- UNIQUE (content_hash));
3290
- CREATE TABLE kb_links (
3291
- from_id text NOT NULL REFERENCES kb_notes(id) ON DELETE CASCADE,
3292
- to_id text NOT NULL REFERENCES kb_notes(id) ON DELETE CASCADE,
3293
- rel text NOT NULL DEFAULT 'related', PRIMARY KEY (from_id, to_id, rel));
3294
- CREATE TABLE trees (
3295
- id text PRIMARY KEY, project_id text REFERENCES projects(id) ON DELETE CASCADE,
3296
- name text NOT NULL UNIQUE, note text DEFAULT '', created text NOT NULL);
3297
- CREATE TABLE nodes (
3298
- id text PRIMARY KEY, tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
3299
- key text NOT NULL, name text NOT NULL, kind text NOT NULL DEFAULT 'tech',
3300
- tier integer NOT NULL DEFAULT 0, cost double precision NOT NULL DEFAULT 0,
3301
- cost_unit text DEFAULT '', payload text DEFAULT '', tags text DEFAULT '',
3302
- created text NOT NULL, UNIQUE (tree_id, key));
3303
- CREATE TABLE node_edges (
3304
- id text PRIMARY KEY, tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
3305
- parent_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3306
- child_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3307
- rel text NOT NULL DEFAULT 'prereq', mode text NOT NULL DEFAULT 'AND',
3308
- required integer NOT NULL DEFAULT 1, min_level integer NOT NULL DEFAULT 1,
3309
- weight double precision NOT NULL DEFAULT 1, note text DEFAULT '',
3310
- UNIQUE (parent_id, child_id, rel));
3311
- CREATE TABLE node_closure (
3312
- tree_id text NOT NULL, ancestor text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3313
- descendant text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3314
- depth integer NOT NULL, PRIMARY KEY (ancestor, descendant));
3315
- CREATE TABLE node_conditions (
3316
- id text PRIMARY KEY, node_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3317
- name text NOT NULL, kind text NOT NULL DEFAULT 'flag', expr text NOT NULL DEFAULT '',
3318
- op text NOT NULL DEFAULT '>=', value text NOT NULL DEFAULT '',
3319
- required integer NOT NULL DEFAULT 1, effect text NOT NULL DEFAULT 'gate',
3320
- weight double precision NOT NULL DEFAULT 1, note text DEFAULT '');
3321
- CREATE TABLE node_state (
3322
- tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
3323
- context text NOT NULL DEFAULT 'default', node_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3324
- status text NOT NULL DEFAULT 'locked', level integer NOT NULL DEFAULT 0,
3325
- progress double precision NOT NULL DEFAULT 0, updated text NOT NULL,
3326
- PRIMARY KEY (tree_id, context, node_id));
3327
- CREATE TABLE node_index (
3328
- tree_id text PRIMARY KEY REFERENCES trees(id) ON DELETE CASCADE,
3329
- topo_order text NOT NULL DEFAULT '', tiers text NOT NULL DEFAULT '',
3330
- in_degree text NOT NULL DEFAULT '', built_at text NOT NULL, dirty integer NOT NULL DEFAULT 1);
3331
- CREATE TABLE relations (
3332
- id text PRIMARY KEY, src_type text NOT NULL, src_id text NOT NULL,
3333
- dst_type text NOT NULL, dst_id text NOT NULL, rel text NOT NULL DEFAULT 'related',
3334
- weight double precision NOT NULL DEFAULT 1,
3335
- UNIQUE (src_type, src_id, dst_type, dst_id, rel));
3336
- CREATE TABLE jobs (
3337
- id text PRIMARY KEY, title text NOT NULL, command text NOT NULL,
3338
- every_minutes integer DEFAULT 0, at text DEFAULT '', enabled integer NOT NULL DEFAULT 1,
3339
- runs integer NOT NULL DEFAULT 0, last_run text DEFAULT '',
3340
- status text NOT NULL DEFAULT 'pending', created text NOT NULL);
3341
- CREATE TABLE runs (
3342
- id text PRIMARY KEY, job_id text NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
3343
- started text NOT NULL, finished text DEFAULT '', rc integer, output text DEFAULT '');
3344
- CREATE TABLE events (
3345
- id text PRIMARY KEY, ts text NOT NULL, actor text NOT NULL DEFAULT 'cli', kind text NOT NULL,
3346
- entity text DEFAULT '', entity_id text DEFAULT '', payload text DEFAULT '',
3347
- level text NOT NULL DEFAULT 'info');
3348
- `;
3349
- const MIGRATE_INDEXES = `
3350
- CREATE INDEX IF NOT EXISTS idx_sources_project ON sources(project_id);
3351
- CREATE INDEX IF NOT EXISTS idx_evidence_project ON evidence(project_id);
3352
- CREATE INDEX IF NOT EXISTS idx_claims_project ON claims(project_id);
3353
- CREATE INDEX IF NOT EXISTS idx_artifacts_proj ON artifacts(project_id);
3354
- CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts);
3355
- CREATE INDEX IF NOT EXISTS idx_jobs_enabled ON jobs(enabled);
3356
- CREATE INDEX IF NOT EXISTS idx_nodes_tree ON nodes(tree_id);
3357
- CREATE INDEX IF NOT EXISTS idx_nodes_tier ON nodes(tree_id, tier);
3358
- CREATE INDEX IF NOT EXISTS idx_edge_child ON node_edges(child_id);
3359
- CREATE INDEX IF NOT EXISTS idx_edge_parent ON node_edges(parent_id);
3360
- CREATE INDEX IF NOT EXISTS idx_edge_req ON node_edges(child_id, required);
3361
- CREATE INDEX IF NOT EXISTS idx_closure_desc ON node_closure(descendant);
3362
- CREATE INDEX IF NOT EXISTS idx_closure_anc ON node_closure(ancestor);
3363
- CREATE INDEX IF NOT EXISTS idx_cond_node ON node_conditions(node_id);
3364
- CREATE INDEX IF NOT EXISTS idx_cond_req ON node_conditions(node_id, required);
3365
- CREATE INDEX IF NOT EXISTS idx_state_ctx ON node_state(tree_id, context);
3366
- CREATE INDEX IF NOT EXISTS idx_state_status ON node_state(tree_id, context, status);
3367
- `;
3368
- /**
3369
- * One-time migration of the whole library into a standalone PG database.
3370
- * Atomic (single transaction: DDL + data + verification), refuses a non-
3371
- * empty target unless `fresh` drops the existing tables first. After
3372
- * migration the CLI keeps working on SQLite only — no dual writes.
3373
- */
3374
- async function migrateToPg(db, url, fresh = false) {
3375
- let pg;
3376
- try {
3377
- pg = require("pg");
3378
- } catch {
3379
- throw new Error("pg driver not installed — run: pnpm add pg (optional dependency)");
3380
- }
3381
- const typeErrors = preflightTypeCheck(db);
3382
- if (typeErrors.length) {
3383
- const lines = typeErrors.slice(0, 5).map((e) => ` ${e.table}.${e.column} rowid=${e.rowid}: value "${e.value}" is not a valid INTEGER`);
3384
- throw new Error(`pre-flight type check failed (${typeErrors.length} issue(s)) — fix these before migrating:\n${lines.join("\n")}${typeErrors.length > 5 ? `\n ...and ${typeErrors.length - 5} more` : ""}\n hint: ygl-trees db sql "UPDATE <table> SET <column>=<int> WHERE rowid=<rowid>"`);
3385
- }
3386
- const client = new pg.Client({ connectionString: url });
3387
- await client.connect();
3388
- try {
3389
- const verNum = Number((await client.query("SHOW server_version_num")).rows[0].server_version_num);
3390
- const ver = (await client.query("SELECT version()")).rows[0].version;
3391
- const fulltext = verNum >= 12e4 ? "tsvector+GIN" : "ngram-only(pg<12)";
3392
- if (!fresh) {
3393
- for (const t of MIRROR_TABLES) if ((await client.query(`SELECT 1 AS hit FROM information_schema.tables WHERE table_name=$1`, [t])).rowCount) {
3394
- const n = (await client.query(`SELECT count(*)::int n FROM "${t}"`)).rows[0].n;
3395
- if (n > 0) throw new Error(`target already contains data (table ${t}, ${n} rows) — migration is one-time; use --fresh to drop and recreate, or 'db sync pg' for a refreshable mirror`);
3396
- }
3397
- } else for (const t of [...MIRROR_TABLES].reverse()) await client.query(`DROP TABLE IF EXISTS "${t}" CASCADE`);
3398
- await client.query("BEGIN");
3399
- try {
3400
- await client.query(MIGRATE_DDL);
3401
- await client.query(MIGRATE_INDEXES);
3402
- for (const t of Object.keys(NGRAM_INPUT)) {
3403
- await client.query(`ALTER TABLE "${t}" ADD COLUMN IF NOT EXISTS ngram text NOT NULL DEFAULT ''`);
3404
- if (verNum >= 12e4) {
3405
- await client.query(`ALTER TABLE "${t}" ADD COLUMN IF NOT EXISTS ngram_tsv tsvector
3406
- GENERATED ALWAYS AS (to_tsvector('simple', regexp_replace(coalesce(ngram,''), '[/\\._:,&+()]+', ' ', 'g'))) STORED`);
3407
- await client.query(`CREATE INDEX IF NOT EXISTS "${t}_tsv" ON "${t}" USING GIN (ngram_tsv)`);
3408
- }
3409
- }
3410
- for (const t of MIRROR_TABLES) {
3411
- const src = db.all(`PRAGMA table_info(${t})`).map((c) => c.name).sort();
3412
- const dst = (await client.query(`SELECT column_name FROM information_schema.columns WHERE table_name=$1`, [t])).rows.map((r) => r.column_name);
3413
- const want = new Set(src);
3414
- if (Object.keys(NGRAM_INPUT).includes(t)) {
3415
- want.add("ngram");
3416
- if (verNum >= 12e4) want.add("ngram_tsv");
3417
- }
3418
- const got = new Set(dst);
3419
- if ([...want].filter((c) => !got.has(c)).length || got.size !== want.size) throw new Error(`schema drift on ${t}: migrate DDL out of date (want [${[...want]}]; got [${dst}]) — update MIGRATE_DDL`);
3420
- }
3421
- const result = [];
3422
- for (const t of MIRROR_TABLES) {
3423
- const rows = await copyTable(client, db, t);
3424
- const n = (await client.query(`SELECT count(*)::int n FROM "${t}"`)).rows[0].n;
3425
- if (n !== rows) throw new Error(`count mismatch on ${t}: copied ${rows}, found ${n}`);
3426
- result.push({
3427
- table: t,
3428
- rows
3429
- });
3430
- }
3431
- await client.query("COMMIT");
3432
- return {
3433
- tables: result,
3434
- server_version: ver,
3435
- constraints: {
3436
- fk: (await client.query(`SELECT count(*)::int n FROM information_schema.table_constraints WHERE constraint_type='FOREIGN KEY'`)).rows[0].n,
3437
- unique: (await client.query(`SELECT count(*)::int n FROM information_schema.table_constraints WHERE constraint_type='UNIQUE'`)).rows[0].n
3438
- },
3439
- fulltext
3440
- };
3441
- } catch (e) {
3442
- await client.query("ROLLBACK");
3443
- throw e;
3444
- }
3445
- } finally {
3446
- await client.end();
3447
- }
3448
- }
3449
- /** Pre-flight: scan INTEGER columns for non-integer values (SQLite weak
3450
- * typing allows REAL into INTEGER; PG rejects at migration time with no
3451
- * context). Returns actionable errors with table.column+rowid. */
3452
- function preflightTypeCheck(db) {
3453
- const issues = [];
3454
- const tables = db.all("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE '%_fts%' AND name NOT LIKE '%_config%' AND name NOT LIKE '%_content%' AND name NOT LIKE '%_data%' AND name NOT LIKE '%_docsize%' AND name NOT LIKE '%_idx%' AND name != 'meta'");
3455
- for (const t of tables) {
3456
- const cols = db.all(`PRAGMA table_info(${t.name})`);
3457
- for (const c of cols) {
3458
- if (c.type !== "INTEGER") continue;
3459
- const bad = db.all(`SELECT rowid, ${c.name} AS v FROM ${t.name} WHERE typeof(${c.name}) NOT IN ('integer','null') LIMIT 5`);
3460
- for (const row of bad) issues.push({
3461
- table: t.name,
3462
- column: c.name,
3463
- rowid: row.rowid,
3464
- value: String(row.v)
3465
- });
3466
- }
3467
- }
3468
- return issues;
3469
- }
3470
- //#endregion
3471
- //#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorateMetadata.js
3472
- function __decorateMetadata(k, v) {
3473
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
3474
- }
3475
- var init_decorateMetadata = require_rolldown_runtime.__esmMin((() => {}));
3476
- //#endregion
3477
- //#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorate.js
3478
- function __decorate(decorators, target, key, desc) {
3479
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3480
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3481
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
3482
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3483
- }
3484
- var init_decorate = require_rolldown_runtime.__esmMin((() => {}));
3485
- //#endregion
3486
- //#region packages/research/pipeline/src/entities.ts
3487
- /**
3488
- * Research-domain entities — a typed snapshot of schema v5.
3489
- *
3490
- * Contract with the schema authority:
3491
- * - `synchronize` is ALWAYS false; migrations stay on the PRAGMA
3492
- * user_version track (src/db.ts). If SCHEMA_SQL changes, these
3493
- * entities must be updated in the same commit.
3494
- * - Column names are explicit snake_case to match the database exactly.
3495
- * - FTS5 virtual tables and PRAGMA paths are OUT of entity scope; they
3496
- * stay raw SQL in store.ts (see docs/plan.md TypeORM boundaries).
3497
- */
3498
- var Project, Query, Source, QuerySource, Evidence, Claim, ClaimEvidence, RESEARCH_ENTITIES;
3499
- var init_entities$1 = require_rolldown_runtime.__esmMin((() => {
3500
- init_decorateMetadata();
3501
- init_decorate();
3502
- Project = class Project {
3503
- id;
3504
- name;
3505
- question;
3506
- inclusion;
3507
- exclusion;
3508
- status;
3509
- created;
3510
- updated;
3511
- queries;
3512
- claims;
3513
- sources;
3514
- };
3515
- __decorate([(0, typeorm.PrimaryColumn)({
3516
- name: "id",
3517
- type: "text"
3518
- }), __decorateMetadata("design:type", String)], Project.prototype, "id", void 0);
3519
- __decorate([(0, typeorm.Column)({
3520
- name: "name",
3521
- type: "text"
3522
- }), __decorateMetadata("design:type", String)], Project.prototype, "name", void 0);
3523
- __decorate([(0, typeorm.Column)({
3524
- name: "question",
3525
- type: "text"
3526
- }), __decorateMetadata("design:type", String)], Project.prototype, "question", void 0);
3527
- __decorate([(0, typeorm.Column)({
3528
- name: "inclusion",
3529
- type: "text"
3530
- }), __decorateMetadata("design:type", String)], Project.prototype, "inclusion", void 0);
3531
- __decorate([(0, typeorm.Column)({
3532
- name: "exclusion",
3533
- type: "text"
3534
- }), __decorateMetadata("design:type", String)], Project.prototype, "exclusion", void 0);
3535
- __decorate([(0, typeorm.Column)({
3536
- name: "status",
3537
- type: "text"
3538
- }), __decorateMetadata("design:type", String)], Project.prototype, "status", void 0);
3539
- __decorate([(0, typeorm.Column)({
3540
- name: "created",
3541
- type: "text"
3542
- }), __decorateMetadata("design:type", String)], Project.prototype, "created", void 0);
3543
- __decorate([(0, typeorm.Column)({
3544
- name: "updated",
3545
- type: "text"
3546
- }), __decorateMetadata("design:type", String)], Project.prototype, "updated", void 0);
3547
- __decorate([(0, typeorm.OneToMany)(() => Query, (q) => q.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "queries", void 0);
3548
- __decorate([(0, typeorm.OneToMany)(() => Claim, (c) => c.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "claims", void 0);
3549
- __decorate([(0, typeorm.OneToMany)(() => Source, (s) => s.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "sources", void 0);
3550
- Project = __decorate([(0, typeorm.Entity)({ name: "projects" })], Project);
3551
- Query = class Query {
3552
- id;
3553
- projectId;
3554
- text;
3555
- engine;
3556
- status;
3557
- note;
3558
- contentHash;
3559
- created;
3560
- project;
3561
- querySources;
3562
- };
3563
- __decorate([(0, typeorm.PrimaryColumn)({
3564
- name: "id",
3565
- type: "text"
3566
- }), __decorateMetadata("design:type", String)], Query.prototype, "id", void 0);
3567
- __decorate([(0, typeorm.Column)({
3568
- name: "project_id",
3569
- type: "text"
3570
- }), __decorateMetadata("design:type", String)], Query.prototype, "projectId", void 0);
3571
- __decorate([(0, typeorm.Column)({
3572
- name: "text",
3573
- type: "text"
3574
- }), __decorateMetadata("design:type", String)], Query.prototype, "text", void 0);
3575
- __decorate([(0, typeorm.Column)({
3576
- name: "engine",
3577
- type: "text"
3578
- }), __decorateMetadata("design:type", String)], Query.prototype, "engine", void 0);
3579
- __decorate([(0, typeorm.Column)({
3580
- name: "status",
3581
- type: "text"
3582
- }), __decorateMetadata("design:type", String)], Query.prototype, "status", void 0);
3583
- __decorate([(0, typeorm.Column)({
3584
- name: "note",
3585
- type: "text"
3586
- }), __decorateMetadata("design:type", String)], Query.prototype, "note", void 0);
3587
- __decorate([(0, typeorm.Column)({
3588
- name: "content_hash",
3589
- type: "text"
3590
- }), __decorateMetadata("design:type", String)], Query.prototype, "contentHash", void 0);
3591
- __decorate([(0, typeorm.Column)({
3592
- name: "created",
3593
- type: "text"
3594
- }), __decorateMetadata("design:type", String)], Query.prototype, "created", void 0);
3595
- __decorate([
3596
- (0, typeorm.ManyToOne)(() => Project, (p) => p.queries, { createForeignKeyConstraints: false }),
3597
- (0, typeorm.JoinColumn)({ name: "project_id" }),
3598
- __decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
3599
- ], Query.prototype, "project", void 0);
3600
- __decorate([(0, typeorm.OneToMany)(() => QuerySource, (qs) => qs.query, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Query.prototype, "querySources", void 0);
3601
- Query = __decorate([(0, typeorm.Entity)({ name: "queries" })], Query);
3602
- Source = class Source {
3603
- id;
3604
- projectId;
3605
- url;
3606
- title;
3607
- urlHash;
3608
- contentHash;
3609
- abstract;
3610
- content;
3611
- engine;
3612
- relevance;
3613
- confidence;
3614
- tags;
3615
- retrievedAt;
3616
- created;
3617
- project;
3618
- querySources;
3619
- };
3620
- __decorate([(0, typeorm.PrimaryColumn)({
3621
- name: "id",
3622
- type: "text"
3623
- }), __decorateMetadata("design:type", String)], Source.prototype, "id", void 0);
3624
- __decorate([(0, typeorm.Column)({
3625
- name: "project_id",
3626
- type: "text"
3627
- }), __decorateMetadata("design:type", String)], Source.prototype, "projectId", void 0);
3628
- __decorate([(0, typeorm.Column)({
3629
- name: "url",
3630
- type: "text"
3631
- }), __decorateMetadata("design:type", String)], Source.prototype, "url", void 0);
3632
- __decorate([(0, typeorm.Column)({
3633
- name: "title",
3634
- type: "text"
3635
- }), __decorateMetadata("design:type", String)], Source.prototype, "title", void 0);
3636
- __decorate([(0, typeorm.Column)({
3637
- name: "url_hash",
3638
- type: "text"
3639
- }), __decorateMetadata("design:type", String)], Source.prototype, "urlHash", void 0);
3640
- __decorate([(0, typeorm.Column)({
3641
- name: "content_hash",
3642
- type: "text"
3643
- }), __decorateMetadata("design:type", String)], Source.prototype, "contentHash", void 0);
3644
- __decorate([(0, typeorm.Column)({
3645
- name: "abstract",
3646
- type: "text"
3647
- }), __decorateMetadata("design:type", String)], Source.prototype, "abstract", void 0);
3648
- __decorate([(0, typeorm.Column)({
3649
- name: "content",
3650
- type: "text"
3651
- }), __decorateMetadata("design:type", String)], Source.prototype, "content", void 0);
3652
- __decorate([(0, typeorm.Column)({
3653
- name: "engine",
3654
- type: "text"
3655
- }), __decorateMetadata("design:type", String)], Source.prototype, "engine", void 0);
3656
- __decorate([(0, typeorm.Column)({
3657
- name: "relevance",
3658
- type: "integer"
3659
- }), __decorateMetadata("design:type", Number)], Source.prototype, "relevance", void 0);
3660
- __decorate([(0, typeorm.Column)({
3661
- name: "confidence",
3662
- type: "text"
3663
- }), __decorateMetadata("design:type", String)], Source.prototype, "confidence", void 0);
3664
- __decorate([(0, typeorm.Column)({
3665
- name: "tags",
3666
- type: "text"
3667
- }), __decorateMetadata("design:type", String)], Source.prototype, "tags", void 0);
3668
- __decorate([(0, typeorm.Column)({
3669
- name: "retrieved_at",
3670
- type: "text"
3671
- }), __decorateMetadata("design:type", String)], Source.prototype, "retrievedAt", void 0);
3672
- __decorate([(0, typeorm.Column)({
3673
- name: "created",
3674
- type: "text",
3675
- nullable: true
3676
- }), __decorateMetadata("design:type", String)], Source.prototype, "created", void 0);
3677
- __decorate([
3678
- (0, typeorm.ManyToOne)(() => Project, (p) => p.sources, { createForeignKeyConstraints: false }),
3679
- (0, typeorm.JoinColumn)({ name: "project_id" }),
3680
- __decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
3681
- ], Source.prototype, "project", void 0);
3682
- __decorate([(0, typeorm.OneToMany)(() => QuerySource, (qs) => qs.source, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Source.prototype, "querySources", void 0);
3683
- Source = __decorate([(0, typeorm.Entity)({ name: "sources" })], Source);
3684
- QuerySource = class QuerySource {
3685
- queryId;
3686
- sourceId;
3687
- note;
3688
- created;
3689
- query;
3690
- source;
3691
- };
3692
- __decorate([(0, typeorm.PrimaryColumn)({
3693
- name: "query_id",
3694
- type: "text"
3695
- }), __decorateMetadata("design:type", String)], QuerySource.prototype, "queryId", void 0);
3696
- __decorate([(0, typeorm.PrimaryColumn)({
3697
- name: "source_id",
3698
- type: "text"
3699
- }), __decorateMetadata("design:type", String)], QuerySource.prototype, "sourceId", void 0);
3700
- __decorate([(0, typeorm.Column)({
3701
- name: "note",
3702
- type: "text"
3703
- }), __decorateMetadata("design:type", String)], QuerySource.prototype, "note", void 0);
3704
- __decorate([(0, typeorm.Column)({
3705
- name: "created",
3706
- type: "text"
3707
- }), __decorateMetadata("design:type", String)], QuerySource.prototype, "created", void 0);
3708
- __decorate([
3709
- (0, typeorm.ManyToOne)(() => Query, (q) => q.querySources, { createForeignKeyConstraints: false }),
3710
- (0, typeorm.JoinColumn)({ name: "query_id" }),
3711
- __decorateMetadata("design:type", typeof Query === "undefined" ? Object : Query)
3712
- ], QuerySource.prototype, "query", void 0);
3713
- __decorate([
3714
- (0, typeorm.ManyToOne)(() => Source, (s) => s.querySources, { createForeignKeyConstraints: false }),
3715
- (0, typeorm.JoinColumn)({ name: "source_id" }),
3716
- __decorateMetadata("design:type", typeof Source === "undefined" ? Object : Source)
3717
- ], QuerySource.prototype, "source", void 0);
3718
- QuerySource = __decorate([(0, typeorm.Entity)({ name: "query_sources" })], QuerySource);
3719
- Evidence = class Evidence {
3720
- id;
3721
- projectId;
3722
- sourceId;
3723
- quote;
3724
- note;
3725
- locator;
3726
- contentHash;
3727
- created;
3728
- project;
3729
- source;
3730
- claimEvidence;
3731
- };
3732
- __decorate([(0, typeorm.PrimaryColumn)({
3733
- name: "id",
3734
- type: "text"
3735
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "id", void 0);
3736
- __decorate([(0, typeorm.Column)({
3737
- name: "project_id",
3738
- type: "text"
3739
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "projectId", void 0);
3740
- __decorate([(0, typeorm.Column)({
3741
- name: "source_id",
3742
- type: "text"
3743
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "sourceId", void 0);
3744
- __decorate([(0, typeorm.Column)({
3745
- name: "quote",
3746
- type: "text"
3747
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "quote", void 0);
3748
- __decorate([(0, typeorm.Column)({
3749
- name: "note",
3750
- type: "text"
3751
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "note", void 0);
3752
- __decorate([(0, typeorm.Column)({
3753
- name: "locator",
3754
- type: "text"
3755
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "locator", void 0);
3756
- __decorate([(0, typeorm.Column)({
3757
- name: "content_hash",
3758
- type: "text"
3759
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "contentHash", void 0);
3760
- __decorate([(0, typeorm.Column)({
3761
- name: "created",
3762
- type: "text"
3763
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "created", void 0);
3764
- __decorate([
3765
- (0, typeorm.ManyToOne)(() => Project, void 0, { createForeignKeyConstraints: false }),
3766
- (0, typeorm.JoinColumn)({ name: "project_id" }),
3767
- __decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
3768
- ], Evidence.prototype, "project", void 0);
3769
- __decorate([
3770
- (0, typeorm.ManyToOne)(() => Source, void 0, { createForeignKeyConstraints: false }),
3771
- (0, typeorm.JoinColumn)({ name: "source_id" }),
3772
- __decorateMetadata("design:type", typeof Source === "undefined" ? Object : Source)
3773
- ], Evidence.prototype, "source", void 0);
3774
- __decorate([(0, typeorm.OneToMany)(() => ClaimEvidence, (ce) => ce.evidence, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Evidence.prototype, "claimEvidence", void 0);
3775
- Evidence = __decorate([(0, typeorm.Entity)({ name: "evidence" })], Evidence);
3776
- Claim = class Claim {
3777
- id;
3778
- projectId;
3779
- text;
3780
- status;
3781
- confidence;
3782
- contentHash;
3783
- created;
3784
- updated;
3785
- project;
3786
- claimEvidence;
3787
- };
3788
- __decorate([(0, typeorm.PrimaryColumn)({
3789
- name: "id",
3790
- type: "text"
3791
- }), __decorateMetadata("design:type", String)], Claim.prototype, "id", void 0);
3792
- __decorate([(0, typeorm.Column)({
3793
- name: "project_id",
3794
- type: "text"
3795
- }), __decorateMetadata("design:type", String)], Claim.prototype, "projectId", void 0);
3796
- __decorate([(0, typeorm.Column)({
3797
- name: "text",
3798
- type: "text"
3799
- }), __decorateMetadata("design:type", String)], Claim.prototype, "text", void 0);
3800
- __decorate([(0, typeorm.Column)({
3801
- name: "status",
3802
- type: "text"
3803
- }), __decorateMetadata("design:type", String)], Claim.prototype, "status", void 0);
3804
- __decorate([(0, typeorm.Column)({
3805
- name: "confidence",
3806
- type: "text"
3807
- }), __decorateMetadata("design:type", String)], Claim.prototype, "confidence", void 0);
3808
- __decorate([(0, typeorm.Column)({
3809
- name: "content_hash",
3810
- type: "text"
3811
- }), __decorateMetadata("design:type", String)], Claim.prototype, "contentHash", void 0);
3812
- __decorate([(0, typeorm.Column)({
3813
- name: "created",
3814
- type: "text"
3815
- }), __decorateMetadata("design:type", String)], Claim.prototype, "created", void 0);
3816
- __decorate([(0, typeorm.Column)({
3817
- name: "updated",
3818
- type: "text",
3819
- nullable: true
3820
- }), __decorateMetadata("design:type", String)], Claim.prototype, "updated", void 0);
3821
- __decorate([
3822
- (0, typeorm.ManyToOne)(() => Project, (p) => p.claims, { createForeignKeyConstraints: false }),
3823
- (0, typeorm.JoinColumn)({ name: "project_id" }),
3824
- __decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
3825
- ], Claim.prototype, "project", void 0);
3826
- __decorate([(0, typeorm.OneToMany)(() => ClaimEvidence, (ce) => ce.claim, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Claim.prototype, "claimEvidence", void 0);
3827
- Claim = __decorate([(0, typeorm.Entity)({ name: "claims" })], Claim);
3828
- ClaimEvidence = class ClaimEvidence {
3829
- claimId;
3830
- evidenceId;
3831
- stance;
3832
- claim;
3833
- evidence;
3834
- };
3835
- __decorate([(0, typeorm.PrimaryColumn)({
3836
- name: "claim_id",
3837
- type: "text"
3838
- }), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "claimId", void 0);
3839
- __decorate([(0, typeorm.PrimaryColumn)({
3840
- name: "evidence_id",
3841
- type: "text"
3842
- }), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "evidenceId", void 0);
3843
- __decorate([(0, typeorm.Column)({
3844
- name: "stance",
3845
- type: "text"
3846
- }), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "stance", void 0);
3847
- __decorate([
3848
- (0, typeorm.ManyToOne)(() => Claim, (c) => c.claimEvidence, { createForeignKeyConstraints: false }),
3849
- (0, typeorm.JoinColumn)({ name: "claim_id" }),
3850
- __decorateMetadata("design:type", typeof Claim === "undefined" ? Object : Claim)
3851
- ], ClaimEvidence.prototype, "claim", void 0);
3852
- __decorate([
3853
- (0, typeorm.ManyToOne)(() => Evidence, void 0, { createForeignKeyConstraints: false }),
3854
- (0, typeorm.JoinColumn)({ name: "evidence_id" }),
3855
- __decorateMetadata("design:type", typeof Evidence === "undefined" ? Object : Evidence)
3856
- ], ClaimEvidence.prototype, "evidence", void 0);
3857
- ClaimEvidence = __decorate([(0, typeorm.Entity)({ name: "claim_evidence" })], ClaimEvidence);
3858
- RESEARCH_ENTITIES = [
3859
- Project,
3860
- Query,
3861
- Source,
3862
- QuerySource,
3863
- Evidence,
3864
- Claim,
3865
- ClaimEvidence
3866
- ];
3867
- }));
3868
- //#endregion
3869
- //#region packages/core/database/src/entities.ts
3870
- /** Cross-cutting entities (audit trail). Research-domain entities live in
3871
- * research.ts; both are registered in src/orm.ts. */
3872
- var Event, CORE_ENTITIES;
3873
- var init_entities = require_rolldown_runtime.__esmMin((() => {
3874
- init_decorateMetadata();
3875
- init_decorate();
3876
- Event = class Event {
3877
- id;
3878
- ts;
3879
- actor;
3880
- kind;
3881
- entity;
3882
- entityId;
3883
- payload;
3884
- /** v7 telemetry severity: info | warn | error (spec R10.2) */
3885
- level;
3886
- };
3887
- __decorate([(0, typeorm.PrimaryColumn)({
3888
- name: "id",
3889
- type: "text"
3890
- }), __decorateMetadata("design:type", String)], Event.prototype, "id", void 0);
3891
- __decorate([(0, typeorm.Column)({
3892
- name: "ts",
3893
- type: "text"
3894
- }), __decorateMetadata("design:type", String)], Event.prototype, "ts", void 0);
3895
- __decorate([(0, typeorm.Column)({
3896
- name: "actor",
3897
- type: "text"
3898
- }), __decorateMetadata("design:type", String)], Event.prototype, "actor", void 0);
3899
- __decorate([(0, typeorm.Column)({
3900
- name: "kind",
3901
- type: "text"
3902
- }), __decorateMetadata("design:type", String)], Event.prototype, "kind", void 0);
3903
- __decorate([(0, typeorm.Column)({
3904
- name: "entity",
3905
- type: "text"
3906
- }), __decorateMetadata("design:type", String)], Event.prototype, "entity", void 0);
3907
- __decorate([(0, typeorm.Column)({
3908
- name: "entity_id",
3909
- type: "text"
3910
- }), __decorateMetadata("design:type", String)], Event.prototype, "entityId", void 0);
3911
- __decorate([(0, typeorm.Column)({
3912
- name: "payload",
3913
- type: "text"
3914
- }), __decorateMetadata("design:type", String)], Event.prototype, "payload", void 0);
3915
- __decorate([(0, typeorm.Column)({
3916
- name: "level",
3917
- type: "text",
3918
- default: "info"
3919
- }), __decorateMetadata("design:type", String)], Event.prototype, "level", void 0);
3920
- Event = __decorate([(0, typeorm.Entity)({ name: "events" })], Event);
3921
- CORE_ENTITIES = [Event];
3922
- }));
3923
- //#endregion
3924
- //#region packages/core/database/src/orm.ts
3866
+ evidence_id text NOT NULL REFERENCES evidence(id) ON DELETE CASCADE,
3867
+ stance text NOT NULL DEFAULT 'supports', PRIMARY KEY (claim_id, evidence_id));
3868
+ CREATE TABLE artifacts (
3869
+ id text PRIMARY KEY, project_id text NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
3870
+ kind text NOT NULL DEFAULT 'section', title text NOT NULL DEFAULT '',
3871
+ format text NOT NULL DEFAULT 'markdown', content text NOT NULL DEFAULT '',
3872
+ version integer NOT NULL DEFAULT 1, created text NOT NULL,
3873
+ meta text NOT NULL DEFAULT '');
3874
+ CREATE TABLE kb_notes (
3875
+ id text PRIMARY KEY, title text NOT NULL, body text NOT NULL DEFAULT '',
3876
+ source_url text DEFAULT '', origin text DEFAULT '', tags text DEFAULT '',
3877
+ confidence text NOT NULL DEFAULT 'B', reuse integer NOT NULL DEFAULT 0,
3878
+ content_hash text NOT NULL DEFAULT '', created text NOT NULL, updated text NOT NULL,
3879
+ UNIQUE (content_hash));
3880
+ CREATE TABLE kb_links (
3881
+ from_id text NOT NULL REFERENCES kb_notes(id) ON DELETE CASCADE,
3882
+ to_id text NOT NULL REFERENCES kb_notes(id) ON DELETE CASCADE,
3883
+ rel text NOT NULL DEFAULT 'related', PRIMARY KEY (from_id, to_id, rel));
3884
+ CREATE TABLE trees (
3885
+ id text PRIMARY KEY, project_id text REFERENCES projects(id) ON DELETE CASCADE,
3886
+ name text NOT NULL UNIQUE, note text DEFAULT '', created text NOT NULL);
3887
+ CREATE TABLE nodes (
3888
+ id text PRIMARY KEY, tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
3889
+ key text NOT NULL, name text NOT NULL, kind text NOT NULL DEFAULT 'tech',
3890
+ tier integer NOT NULL DEFAULT 0, cost double precision NOT NULL DEFAULT 0,
3891
+ cost_unit text DEFAULT '', payload text DEFAULT '', tags text DEFAULT '',
3892
+ created text NOT NULL, UNIQUE (tree_id, key));
3893
+ CREATE TABLE node_edges (
3894
+ id text PRIMARY KEY, tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
3895
+ parent_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3896
+ child_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3897
+ rel text NOT NULL DEFAULT 'prereq', mode text NOT NULL DEFAULT 'AND',
3898
+ required integer NOT NULL DEFAULT 1, min_level integer NOT NULL DEFAULT 1,
3899
+ weight double precision NOT NULL DEFAULT 1, note text DEFAULT '',
3900
+ UNIQUE (parent_id, child_id, rel));
3901
+ CREATE TABLE node_closure (
3902
+ tree_id text NOT NULL, ancestor text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3903
+ descendant text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3904
+ depth integer NOT NULL, PRIMARY KEY (ancestor, descendant));
3905
+ CREATE TABLE node_conditions (
3906
+ id text PRIMARY KEY, node_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3907
+ name text NOT NULL, kind text NOT NULL DEFAULT 'flag', expr text NOT NULL DEFAULT '',
3908
+ op text NOT NULL DEFAULT '>=', value text NOT NULL DEFAULT '',
3909
+ required integer NOT NULL DEFAULT 1, effect text NOT NULL DEFAULT 'gate',
3910
+ weight double precision NOT NULL DEFAULT 1, note text DEFAULT '');
3911
+ CREATE TABLE node_state (
3912
+ tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
3913
+ context text NOT NULL DEFAULT 'default', node_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3914
+ status text NOT NULL DEFAULT 'locked', level integer NOT NULL DEFAULT 0,
3915
+ progress double precision NOT NULL DEFAULT 0, updated text NOT NULL,
3916
+ PRIMARY KEY (tree_id, context, node_id));
3917
+ CREATE TABLE node_index (
3918
+ tree_id text PRIMARY KEY REFERENCES trees(id) ON DELETE CASCADE,
3919
+ topo_order text NOT NULL DEFAULT '', tiers text NOT NULL DEFAULT '',
3920
+ in_degree text NOT NULL DEFAULT '', built_at text NOT NULL, dirty integer NOT NULL DEFAULT 1);
3921
+ CREATE TABLE relations (
3922
+ id text PRIMARY KEY, src_type text NOT NULL, src_id text NOT NULL,
3923
+ dst_type text NOT NULL, dst_id text NOT NULL, rel text NOT NULL DEFAULT 'related',
3924
+ weight double precision NOT NULL DEFAULT 1,
3925
+ UNIQUE (src_type, src_id, dst_type, dst_id, rel));
3926
+ CREATE TABLE jobs (
3927
+ id text PRIMARY KEY, title text NOT NULL, command text NOT NULL,
3928
+ every_minutes integer DEFAULT 0, at text DEFAULT '', enabled integer NOT NULL DEFAULT 1,
3929
+ runs integer NOT NULL DEFAULT 0, last_run text DEFAULT '',
3930
+ status text NOT NULL DEFAULT 'pending', created text NOT NULL);
3931
+ CREATE TABLE runs (
3932
+ id text PRIMARY KEY, job_id text NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
3933
+ started text NOT NULL, finished text DEFAULT '', rc integer, output text DEFAULT '');
3934
+ CREATE TABLE events (
3935
+ id text PRIMARY KEY, ts text NOT NULL, actor text NOT NULL DEFAULT 'cli', kind text NOT NULL,
3936
+ entity text DEFAULT '', entity_id text DEFAULT '', payload text DEFAULT '',
3937
+ level text NOT NULL DEFAULT 'info');
3938
+ `;
3939
+ const MIGRATE_INDEXES = `
3940
+ CREATE INDEX IF NOT EXISTS idx_sources_project ON sources(project_id);
3941
+ CREATE INDEX IF NOT EXISTS idx_evidence_project ON evidence(project_id);
3942
+ CREATE INDEX IF NOT EXISTS idx_claims_project ON claims(project_id);
3943
+ CREATE INDEX IF NOT EXISTS idx_artifacts_proj ON artifacts(project_id);
3944
+ CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts);
3945
+ CREATE INDEX IF NOT EXISTS idx_jobs_enabled ON jobs(enabled);
3946
+ CREATE INDEX IF NOT EXISTS idx_nodes_tree ON nodes(tree_id);
3947
+ CREATE INDEX IF NOT EXISTS idx_nodes_tier ON nodes(tree_id, tier);
3948
+ CREATE INDEX IF NOT EXISTS idx_edge_child ON node_edges(child_id);
3949
+ CREATE INDEX IF NOT EXISTS idx_edge_parent ON node_edges(parent_id);
3950
+ CREATE INDEX IF NOT EXISTS idx_edge_req ON node_edges(child_id, required);
3951
+ CREATE INDEX IF NOT EXISTS idx_closure_desc ON node_closure(descendant);
3952
+ CREATE INDEX IF NOT EXISTS idx_closure_anc ON node_closure(ancestor);
3953
+ CREATE INDEX IF NOT EXISTS idx_cond_node ON node_conditions(node_id);
3954
+ CREATE INDEX IF NOT EXISTS idx_cond_req ON node_conditions(node_id, required);
3955
+ CREATE INDEX IF NOT EXISTS idx_state_ctx ON node_state(tree_id, context);
3956
+ CREATE INDEX IF NOT EXISTS idx_state_status ON node_state(tree_id, context, status);
3957
+ `;
3925
3958
  /**
3926
- * Lazy TypeORM DataSource over the SAME SQLite file the CLI works on.
3927
- *
3928
- * Boundaries (see docs/plan.md "TypeORM boundaries"):
3929
- * - `synchronize` is permanently false — schema authority stays with
3930
- * SCHEMA_SQL + PRAGMA user_version migrations in src/db.ts. Entities in
3931
- * src/entities/ are a typed snapshot; change them in the same commit as
3932
- * SCHEMA_SQL.
3933
- * - TypeORM is a QUERY layer here, not a migration layer. FTS5 MATCH /
3934
- * bm25 loops, PRAGMA paths and the PG sync/migrate machinery stay raw
3935
- * SQL on the DB class.
3936
- * - CLI commands using the ORM must return a Promise (main() awaits) so
3937
- * the DataSource settles before the wasm DB closes.
3959
+ * One-time migration of the whole library into a standalone PG database.
3960
+ * Atomic (single transaction: DDL + data + verification), refuses a non-
3961
+ * empty target unless `fresh` drops the existing tables first. After
3962
+ * migration the CLI keeps working on SQLite only — no dual writes.
3938
3963
  */
3939
- var orm_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
3940
- closeOrm: () => closeOrm,
3941
- getOrm: () => getOrm
3942
- });
3943
- async function getOrm(dbFile) {
3944
- if (cached && cached.file === dbFile) return cached.ds;
3945
- const ds = new typeorm.DataSource({
3946
- type: "better-sqlite3",
3947
- database: dbFile,
3948
- synchronize: false,
3949
- entities: [...RESEARCH_ENTITIES, ...CORE_ENTITIES]
3950
- });
3951
- await ds.initialize();
3952
- await ds.query("PRAGMA synchronous=NORMAL");
3953
- cached = {
3954
- file: dbFile,
3955
- ds
3956
- };
3957
- return ds;
3958
- }
3959
- /** Destroy the cached DataSource (tests/scratch dirs cannot be removed on
3960
- * Windows while a better-sqlite3 handle keeps the file open — EPERM). */
3961
- async function closeOrm() {
3962
- if (cached) {
3964
+ async function migrateToPg(db, url, fresh = false) {
3965
+ let pg;
3966
+ try {
3967
+ pg = require("pg");
3968
+ } catch {
3969
+ throw new Error("pg driver not installed — run: pnpm add pg (optional dependency)");
3970
+ }
3971
+ const typeErrors = preflightTypeCheck(db);
3972
+ if (typeErrors.length) {
3973
+ const lines = typeErrors.slice(0, 5).map((e) => ` ${e.table}.${e.column} rowid=${e.rowid}: value "${e.value}" is not a valid INTEGER`);
3974
+ throw new Error(`pre-flight type check failed (${typeErrors.length} issue(s)) — fix these before migrating:\n${lines.join("\n")}${typeErrors.length > 5 ? `\n ...and ${typeErrors.length - 5} more` : ""}\n hint: ygl-trees db sql "UPDATE <table> SET <column>=<int> WHERE rowid=<rowid>"`);
3975
+ }
3976
+ const client = new pg.Client({ connectionString: url });
3977
+ await client.connect();
3978
+ try {
3979
+ const verNum = Number((await client.query("SHOW server_version_num")).rows[0].server_version_num);
3980
+ const ver = (await client.query("SELECT version()")).rows[0].version;
3981
+ const fulltext = verNum >= 12e4 ? "tsvector+GIN" : "ngram-only(pg<12)";
3982
+ if (!fresh) {
3983
+ for (const t of MIRROR_TABLES) if ((await client.query(`SELECT 1 AS hit FROM information_schema.tables WHERE table_name=$1`, [t])).rowCount) {
3984
+ const n = (await client.query(`SELECT count(*)::int n FROM "${t}"`)).rows[0].n;
3985
+ if (n > 0) throw new Error(`target already contains data (table ${t}, ${n} rows) — migration is one-time; use --fresh to drop and recreate, or 'db sync pg' for a refreshable mirror`);
3986
+ }
3987
+ } else for (const t of [...MIRROR_TABLES].reverse()) await client.query(`DROP TABLE IF EXISTS "${t}" CASCADE`);
3988
+ await client.query("BEGIN");
3963
3989
  try {
3964
- await cached.ds.destroy();
3965
- } catch {}
3966
- cached = null;
3990
+ await client.query(MIGRATE_DDL);
3991
+ await client.query(MIGRATE_INDEXES);
3992
+ for (const t of Object.keys(NGRAM_INPUT)) {
3993
+ await client.query(`ALTER TABLE "${t}" ADD COLUMN IF NOT EXISTS ngram text NOT NULL DEFAULT ''`);
3994
+ if (verNum >= 12e4) {
3995
+ await client.query(`ALTER TABLE "${t}" ADD COLUMN IF NOT EXISTS ngram_tsv tsvector
3996
+ GENERATED ALWAYS AS (to_tsvector('simple', regexp_replace(coalesce(ngram,''), '[/\\._:,&+()]+', ' ', 'g'))) STORED`);
3997
+ await client.query(`CREATE INDEX IF NOT EXISTS "${t}_tsv" ON "${t}" USING GIN (ngram_tsv)`);
3998
+ }
3999
+ }
4000
+ for (const t of MIRROR_TABLES) {
4001
+ const src = db.all(`PRAGMA table_info(${t})`).map((c) => c.name).sort();
4002
+ const dst = (await client.query(`SELECT column_name FROM information_schema.columns WHERE table_name=$1`, [t])).rows.map((r) => r.column_name);
4003
+ const want = new Set(src);
4004
+ if (Object.keys(NGRAM_INPUT).includes(t)) {
4005
+ want.add("ngram");
4006
+ if (verNum >= 12e4) want.add("ngram_tsv");
4007
+ }
4008
+ const got = new Set(dst);
4009
+ if ([...want].filter((c) => !got.has(c)).length || got.size !== want.size) throw new Error(`schema drift on ${t}: migrate DDL out of date (want [${[...want]}]; got [${dst}]) — update MIGRATE_DDL`);
4010
+ }
4011
+ const result = [];
4012
+ for (const t of MIRROR_TABLES) {
4013
+ const rows = await copyTable(client, db, t);
4014
+ const n = (await client.query(`SELECT count(*)::int n FROM "${t}"`)).rows[0].n;
4015
+ if (n !== rows) throw new Error(`count mismatch on ${t}: copied ${rows}, found ${n}`);
4016
+ result.push({
4017
+ table: t,
4018
+ rows
4019
+ });
4020
+ }
4021
+ await client.query("COMMIT");
4022
+ return {
4023
+ tables: result,
4024
+ server_version: ver,
4025
+ constraints: {
4026
+ fk: (await client.query(`SELECT count(*)::int n FROM information_schema.table_constraints WHERE constraint_type='FOREIGN KEY'`)).rows[0].n,
4027
+ unique: (await client.query(`SELECT count(*)::int n FROM information_schema.table_constraints WHERE constraint_type='UNIQUE'`)).rows[0].n
4028
+ },
4029
+ fulltext
4030
+ };
4031
+ } catch (e) {
4032
+ await client.query("ROLLBACK");
4033
+ throw e;
4034
+ }
4035
+ } finally {
4036
+ await client.end();
3967
4037
  }
3968
4038
  }
3969
- var cached;
3970
- var init_orm = require_rolldown_runtime.__esmMin((() => {
3971
- init_entities$1();
3972
- init_entities();
3973
- cached = null;
3974
- }));
4039
+ /** Pre-flight: scan INTEGER columns for non-integer values (SQLite weak
4040
+ * typing allows REAL into INTEGER; PG rejects at migration time with no
4041
+ * context). Returns actionable errors with table.column+rowid. */
4042
+ function preflightTypeCheck(db) {
4043
+ const issues = [];
4044
+ const tables = db.all("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE '%_fts%' AND name NOT LIKE '%_config%' AND name NOT LIKE '%_content%' AND name NOT LIKE '%_data%' AND name NOT LIKE '%_docsize%' AND name NOT LIKE '%_idx%' AND name != 'meta'");
4045
+ for (const t of tables) {
4046
+ const cols = db.all(`PRAGMA table_info(${t.name})`);
4047
+ for (const c of cols) {
4048
+ if (c.type !== "INTEGER") continue;
4049
+ const bad = db.all(`SELECT rowid, ${c.name} AS v FROM ${t.name} WHERE typeof(${c.name}) NOT IN ('integer','null') LIMIT 5`);
4050
+ for (const row of bad) issues.push({
4051
+ table: t.name,
4052
+ column: c.name,
4053
+ rowid: row.rowid,
4054
+ value: String(row.v)
4055
+ });
4056
+ }
4057
+ }
4058
+ return issues;
4059
+ }
3975
4060
  //#endregion
3976
4061
  //#region packages/research/pipeline/src/writes.ts
3977
4062
  var writes_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
@@ -4013,6 +4098,16 @@ async function logEvent$1(mgr, kind, entity, entityId, payload, level = "info")
4013
4098
  payload: p,
4014
4099
  level
4015
4100
  });
4101
+ try {
4102
+ const { emitEvent } = (init_eventbus(), require_rolldown_runtime.__toCommonJS(eventbus_exports));
4103
+ emitEvent({
4104
+ kind,
4105
+ entity,
4106
+ entityId,
4107
+ payload: String(payload),
4108
+ ts: (/* @__PURE__ */ new Date()).toISOString()
4109
+ });
4110
+ } catch {}
4016
4111
  }
4017
4112
  async function queryAdd(ds, projectId, text, engine = "", force = false) {
4018
4113
  const hash = contentHash(text, force);
@@ -7187,6 +7282,16 @@ async function logEvent(mgr, kind, entityId, payload, level = "info") {
7187
7282
  body,
7188
7283
  level
7189
7284
  ]);
7285
+ try {
7286
+ const { emitEvent } = (init_eventbus(), require_rolldown_runtime.__toCommonJS(eventbus_exports));
7287
+ emitEvent({
7288
+ kind,
7289
+ entity: "node",
7290
+ entityId,
7291
+ payload: String(body),
7292
+ ts: (/* @__PURE__ */ new Date()).toISOString()
7293
+ });
7294
+ } catch {}
7190
7295
  }
7191
7296
  async function reaches(mgr, treeId, fromId, toId) {
7192
7297
  return (await mgr.query(`SELECT 1 AS hit FROM node_closure WHERE tree_id=? AND ancestor=? AND descendant=? AND depth>0`, [
@@ -8750,7 +8855,23 @@ function run$4(env, action, rest, args) {
8750
8855
  return 0;
8751
8856
  case "link": {
8752
8857
  const os = require("node:os");
8753
- const targetDir = args?.flags?.global === true ? node_path.join(os.homedir(), ".agents", "skills") : node_path.join(process.cwd(), ".agents", "skills");
8858
+ const globalFlag = args?.flags?.global === true;
8859
+ const aiName = String(args?.flags?.ai || "").toLowerCase();
8860
+ const customTo = String(args?.flags?.to || "");
8861
+ const AI_SKILL_DIRS = {
8862
+ pi: () => node_path.join(os.homedir(), ".pi", "agent", "skills"),
8863
+ claude: () => node_path.join(os.homedir(), ".claude", "skills"),
8864
+ cursor: () => node_path.join(os.homedir(), ".cursor", "skills"),
8865
+ windsurf: () => node_path.join(os.homedir(), ".windsurf", "skills")
8866
+ };
8867
+ let targetDir;
8868
+ if (customTo) targetDir = node_path.resolve(customTo);
8869
+ else if (aiName && AI_SKILL_DIRS[aiName]) targetDir = AI_SKILL_DIRS[aiName]();
8870
+ else if (aiName && aiName !== "custom") {
8871
+ bad(ctx, `unknown AI agent: ${aiName}`);
8872
+ out(ctx, ` supported: ${Object.keys(AI_SKILL_DIRS).join(", ")} | custom (with --to <path>)`);
8873
+ return 2;
8874
+ } else targetDir = globalFlag ? node_path.join(os.homedir(), ".agents", "skills") : node_path.join(process.cwd(), ".agents", "skills");
8754
8875
  let skillsDir = null;
8755
8876
  for (let dir = process.cwd(), i = 0; i < 6; i++, dir = node_path.dirname(dir)) {
8756
8877
  const candidate = node_path.join(dir, "skills");
@@ -10360,6 +10481,15 @@ function createDashboardServer(env) {
10360
10481
  const h = setInterval(tick, 1500);
10361
10482
  if (typeof h.unref === "function") h.unref();
10362
10483
  }
10484
+ let unsubBus = null;
10485
+ try {
10486
+ const { onEvent } = (init_eventbus(), require_rolldown_runtime.__toCommonJS(eventbus_exports));
10487
+ unsubBus = onEvent((evt) => {
10488
+ if (!sseClients.size) return;
10489
+ push("data", gatherData(env));
10490
+ push(evt.kind.replace(/\//g, "-"), evt);
10491
+ });
10492
+ } catch {}
10363
10493
  const server = http.createServer((req, res) => {
10364
10494
  const u = new URL(req.url || "/", "http://127.0.0.1");
10365
10495
  const url = u.pathname;
@@ -10457,7 +10587,15 @@ function createDashboardServer(env) {
10457
10587
  sseClients.clear();
10458
10588
  };
10459
10589
  server.on("close", dropLive);
10460
- server.closeLive = dropLive;
10590
+ server.closeLive = () => {
10591
+ dropLive();
10592
+ if (unsubBus) {
10593
+ try {
10594
+ unsubBus();
10595
+ } catch {}
10596
+ unsubBus = null;
10597
+ }
10598
+ };
10461
10599
  return server;
10462
10600
  }
10463
10601
  /** Live dashboard: blocks until SIGINT/SIGTERM, then resolves so the CLI can
@@ -11217,6 +11355,9 @@ async function main(argv) {
11217
11355
  return 1;
11218
11356
  }).finally(() => {
11219
11357
  try {
11358
+ if (booted?.ctx?.database?.stop) booted.ctx.database.stop();
11359
+ } catch {}
11360
+ if (!booted?.ctx?.database) try {
11220
11361
  db?.close();
11221
11362
  } catch {}
11222
11363
  });