ygl-trees 0.75.0 → 0.76.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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");
13
15
  let _deepseek_ai_cordis_plugin_timer = require("@deepseek-ai/cordis-plugin-timer");
14
16
  _deepseek_ai_cordis_plugin_timer = require_rolldown_runtime.__toESM(_deepseek_ai_cordis_plugin_timer);
15
17
  let node_child_process = require("node:child_process");
16
18
  let _deepseek_ai_dsh_atomic_write = require("@deepseek-ai/dsh-atomic-write");
17
- require("reflect-metadata");
18
- let typeorm = require("typeorm");
19
+ let node_events = require("node:events");
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
@@ -778,16 +779,20 @@ CREATE UNIQUE INDEX IF NOT EXISTS uq_skill_refs ON skill_refs(skill_id, path);
778
779
  }));
779
780
  //#endregion
780
781
  //#region packages/core/text/src/fmt.ts
781
- init_db();
782
- const C = {
783
- reset: "\x1B[0m",
784
- bold: "\x1B[1m",
785
- dim: "\x1B[2m",
786
- red: "\x1B[31m",
787
- green: "\x1B[32m",
788
- yellow: "\x1B[33m",
789
- cyan: "\x1B[36m"
790
- };
782
+ var fmt_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
783
+ C: () => C,
784
+ ack: () => ack,
785
+ bad: () => bad,
786
+ dump: () => dump,
787
+ head: () => head,
788
+ ok: () => ok,
789
+ out: () => out,
790
+ paint: () => paint,
791
+ show: () => show,
792
+ table: () => table,
793
+ warn: () => warn,
794
+ writeResult: () => writeResult
795
+ });
791
796
  function paint(code, s, on) {
792
797
  return on ? `${code}${s}${C.reset}` : s;
793
798
  }
@@ -797,6 +802,9 @@ function out(ctx, msg = "") {
797
802
  function ok(ctx, msg) {
798
803
  out(ctx, paint(C.green, ` ✓ ${msg}`, ctx.color));
799
804
  }
805
+ function warn(ctx, msg) {
806
+ out(ctx, paint(C.yellow, ` ! ${msg}`, ctx.color));
807
+ }
800
808
  /**
801
809
  * Failure report. In --json mode stdout is the MACHINE channel, so a failure
802
810
  * must still be parseable there: a single `{"error": "..."}` object (the exit
@@ -858,6 +866,10 @@ function show(ctx, obj, title) {
858
866
  out(ctx, ` ${paint(C.dim, k.padEnd(width), ctx.color)} ${val}`);
859
867
  }
860
868
  }
869
+ function dump(ctx, data) {
870
+ if (ctx.json) console.log(JSON.stringify(data, null, 2));
871
+ else console.log(typeof data === "string" ? data : JSON.stringify(data, null, 2));
872
+ }
861
873
  /**
862
874
  * Write-path result printer with JSON discipline: under --json the ONLY
863
875
  * stdout is one JSON object (duplicate or new, duplicates carry
@@ -881,8 +893,22 @@ function ack(ctx, payload, msg) {
881
893
  if (ctx.json) console.log(JSON.stringify(payload, null, 2));
882
894
  else ok(ctx, msg);
883
895
  }
896
+ var C;
897
+ var init_fmt = require_rolldown_runtime.__esmMin((() => {
898
+ C = {
899
+ reset: "\x1B[0m",
900
+ bold: "\x1B[1m",
901
+ dim: "\x1B[2m",
902
+ red: "\x1B[31m",
903
+ green: "\x1B[32m",
904
+ yellow: "\x1B[33m",
905
+ cyan: "\x1B[36m"
906
+ };
907
+ }));
884
908
  //#endregion
885
909
  //#region packages/core/database/src/types.ts
910
+ init_db();
911
+ init_fmt();
886
912
  const BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
887
913
  "json",
888
914
  "no-color",
@@ -946,397 +972,917 @@ function bool(args, key) {
946
972
  return v === true || v === "true" || v === "1";
947
973
  }
948
974
  //#endregion
949
- //#region packages/core/database/src/plugin.ts
950
- /**
951
- * DatabasePlugin — provides ctx.database (DatabaseService).
952
- * This is the entry point that apps compose via ctx.plugin().
953
- *
954
- * @module @ygl/core-database/plugin
955
- */
956
- var DatabaseService = class extends _deepseek_ai_cordis.Service {
957
- db;
958
- constructor(ctx, dbFile) {
959
- super(ctx, "database");
960
- const lock = process.env.YGL_TEST !== "1" && process.env.YGL_SERVE !== "1";
961
- this.db = new DB(dbFile, { lock });
962
- }
963
- };
964
- function apply$3(ctx, config) {
965
- 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);
966
978
  }
979
+ var init_decorateMetadata = require_rolldown_runtime.__esmMin((() => {}));
967
980
  //#endregion
968
- //#region packages/web/app/src/index.ts
969
- /**
970
- * WebAppPlugin — provides ctx.webApp: serves the built React client
971
- * (apps/web/dist, workspace knowledge — never user config) over the
972
- * dashboard server. Pattern follows DSH packages/bundle/web-app: the app
973
- * entry builds with vite, this runtime-glue plugin resolves the dist and
974
- * hands static-file serving to whoever owns the HTTP surface.
975
- *
976
- * @module @ygl/web-app
977
- */
978
- /** Resolve the built client dist (workspace knowledge). Pure: safe to call
979
- * from short-lived CLI processes without betting on async plugin timing.
980
- *
981
- * Probe by REPO MARKER, not by counting levels from __dirname: under the
982
- * tsdown single-file build __dirname collapses to dist/bundle, so any
983
- * fixed-depth relative path misses. Walking up to the repo root (the dir
984
- * holding ygl.bundle.json) works from every layout — tsc tree, bundle,
985
- * and a run straight from src/. */
986
- function resolveDistRoot(from = __dirname) {
987
- const candidates = [];
988
- let dir = from;
989
- for (let i = 0; i < 6; i++, dir = node_path.dirname(dir)) {
990
- candidates.push(node_path.join(dir, "apps", "web", "dist"));
991
- if (node_fs.existsSync(node_path.join(dir, "ygl.bundle.json"))) break;
992
- }
993
- candidates.push(node_path.resolve(process.cwd(), "apps", "web", "dist"));
994
- for (const candidate of candidates) if (node_fs.existsSync(node_path.join(candidate, "index.html"))) return candidate;
995
- return null;
996
- }
997
- const MIME = {
998
- ".html": "text/html; charset=utf-8",
999
- ".js": "text/javascript; charset=utf-8",
1000
- ".mjs": "text/javascript; charset=utf-8",
1001
- ".css": "text/css; charset=utf-8",
1002
- ".json": "application/json",
1003
- ".svg": "image/svg+xml",
1004
- ".png": "image/png",
1005
- ".ico": "image/x-icon",
1006
- ".woff2": "font/woff2",
1007
- ".map": "application/json"
1008
- };
1009
- /** URL → dist 静态文件(SPA 回落 index.html;路径遍历安全)。纯函数。 */
1010
- function openStatic(urlPath) {
1011
- const root = resolveDistRoot();
1012
- if (!root) return null;
1013
- const clean = urlPath.split("?")[0].replace(/^\/+/, "");
1014
- let file = node_path.resolve(root, clean === "" ? "index.html" : clean);
1015
- if (!file.startsWith(root)) file = node_path.join(root, "index.html");
1016
- if (!node_fs.existsSync(file) || node_fs.statSync(file).isDirectory()) {
1017
- file = node_path.join(root, "index.html");
1018
- if (!node_fs.existsSync(file)) return null;
1019
- }
1020
- const ext = node_path.extname(file).toLowerCase();
1021
- return {
1022
- body: node_fs.readFileSync(file),
1023
- mime: MIME[ext] || "application/octet-stream"
1024
- };
1025
- }
1026
- var WebAppService = class extends _deepseek_ai_cordis.Service {
1027
- constructor(ctx) {
1028
- super(ctx, "webApp");
1029
- }
1030
- /** Whether a built client exists to serve. */
1031
- available() {
1032
- return resolveDistRoot() !== null;
1033
- }
1034
- /** Delegates to the pure resolver — short-lived CLI callers may use the
1035
- * standalone openStatic() without waiting on plugin timing. */
1036
- open(urlPath) {
1037
- return openStatic(urlPath);
1038
- }
1039
- };
1040
- function apply$2(ctx) {
1041
- 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;
1042
987
  }
988
+ var init_decorate = require_rolldown_runtime.__esmMin((() => {}));
1043
989
  //#endregion
1044
- //#region packages/techtree/eval/src/eval.ts
990
+ //#region packages/research/pipeline/src/entities.ts
1045
991
  /**
1046
- * Decision-model boolean evaluator — spec R8 (docs/spec.md).
1047
- *
1048
- * Speaks the Vercel AI Gateway v4 evaluation-model protocol (TypeSafe jev):
1049
- * deterministic P(true) judgments, 1-2s/call, native CJK, collapses toward
1050
- * the gray band instead of hard-answering on thin evidence. Material:
1051
- * F:/Users/Administrator/Downloads/vercel-ai-gateway-eval-api.md.
992
+ * Research-domain entities — a typed snapshot of schema v5.
1052
993
  *
1053
- * Offline-safe by design: callers map "no token / network failure" to a
1054
- * PENDING gate outcome (never a hard fail) — the evaluator is an
1055
- * 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).
1056
1001
  */
1057
- /** Normalize a configured api_base: a bare host gets the standard v4
1058
- * evaluation-model path appended; a full endpoint is used as-is.
1059
- * Config priority is composed by the CALLER (cmds layer):
1060
- * config(decision_model_llm_api_base) > this default. */
1061
- function resolveEndpoint(base) {
1062
- const v = String(base || "").trim().replace(/\/+$/, "");
1063
- if (!v) return GATEWAY_URL;
1064
- if (/\/v4\/ai\/evaluation-model$/.test(v)) return v;
1065
- return `${v}/v4/ai/evaluation-model`;
1066
- }
1067
- /** Token resolution: env first, then ~/.vercel.ai-gateway.env (token=...).
1068
- * Set YGL_EVAL_OFFLINE=1 to force the offline path (tests/demos on
1069
- * machines that DO have a token file — keeps them deterministic). */
1070
- function evalToken() {
1071
- if (process.env.YGL_EVAL_OFFLINE) return "";
1072
- if (process.env.AI_GATEWAY_API_KEY) return process.env.AI_GATEWAY_API_KEY;
1073
- try {
1074
- const m = /^token\s*=\s*(\S+)/m.exec(node_fs.readFileSync(node_path.join(node_os.homedir(), ".vercel.ai-gateway.env"), "utf8"));
1075
- if (m) return m[1];
1076
- } catch {}
1077
- return "";
1078
- }
1079
- /** One boolean question about a state. Retries 429/529 with exponential
1080
- * backoff (<=3); 400/401 and malformed answers return without retry;
1081
- * 60s timeout per attempt. `endpoint` defaults to the Vercel gateway —
1082
- * callers pass the config-resolved value (decision_model_llm_api_base
1083
- * has priority over the default). */
1084
- async function evalBoolean(state, question, token, endpoint = GATEWAY_URL) {
1085
- if (!token) return {
1086
- ok: false,
1087
- error: NO_TOKEN
1088
- };
1089
- const r = await postEval(state, { q: {
1090
- type: "boolean",
1091
- instructions: question
1092
- } }, token, endpoint);
1093
- if (!r.ok) return {
1094
- ok: false,
1095
- error: r.error
1096
- };
1097
- const p = r.answers.q?.probability;
1098
- if (typeof p !== "number") return {
1099
- ok: false,
1100
- 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;
1101
1018
  };
1102
- return {
1103
- ok: true,
1104
- p,
1105
- 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;
1106
1066
  };
1107
- }
1108
- function parseEvalSpec(value) {
1109
- const s = String(value ?? "").trim();
1110
- if (s.startsWith("{")) try {
1111
- const j = JSON.parse(s);
1112
- if (j && typeof j.q === "string") return j;
1113
- } catch {}
1114
- return { q: s };
1115
- }
1116
- function packEvalChoiceSpec(q, options, answer) {
1117
- return JSON.stringify({
1118
- q,
1119
- options,
1120
- answer
1121
- });
1122
- }
1123
- function packEvalScoreSpec(q, levels, min) {
1124
- return JSON.stringify({
1125
- q,
1126
- levels,
1127
- min
1128
- });
1129
- }
1130
- async function postEval(state, questions, token, endpoint) {
1131
- const url = resolveEndpoint(endpoint);
1132
- const body = JSON.stringify({
1133
- state,
1134
- questions
1135
- });
1136
- for (let attempt = 0;; attempt++) try {
1137
- const res = await fetch(url, {
1138
- method: "POST",
1139
- headers: {
1140
- Authorization: `Bearer ${token}`,
1141
- "Content-Type": "application/json",
1142
- "ai-model-id": MODEL_ID,
1143
- "ai-evaluation-model-specification-version": "4",
1144
- "ai-gateway-protocol-version": "0.0.1",
1145
- "ai-gateway-auth-method": "api-key"
1146
- },
1147
- body,
1148
- signal: AbortSignal.timeout(6e4)
1149
- });
1150
- if (res.status === 429 || res.status === 529) {
1151
- if (attempt >= 3) return {
1152
- ok: false,
1153
- error: `HTTP ${res.status} after ${attempt + 1} attempts`
1154
- };
1155
- await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
1156
- continue;
1157
- }
1158
- if (!res.ok) return {
1159
- ok: false,
1160
- error: `HTTP ${res.status} (protocol/auth error — no retry)`
1161
- };
1162
- const j = await res.json();
1163
- if (!j?.answers) return {
1164
- ok: false,
1165
- error: "malformed response: no answers"
1166
- };
1167
- return {
1168
- ok: true,
1169
- answers: j.answers,
1170
- usage: j.usage ?? void 0
1171
- };
1172
- } catch (e) {
1173
- const msg = String(e?.message || e);
1174
- if (attempt < 3 && /timeout|abort|network|fetch failed|ECONN/i.test(msg)) {
1175
- await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
1176
- continue;
1177
- }
1178
- return {
1179
- ok: false,
1180
- error: msg
1181
- };
1182
- }
1183
- }
1184
- async function evalChoice(state, question, options, token, endpoint = GATEWAY_URL) {
1185
- if (!token) return {
1186
- ok: false,
1187
- error: NO_TOKEN
1188
- };
1189
- const criteria = {};
1190
- for (const [k, v] of Object.entries(options)) criteria[k] = v || null;
1191
- const r = await postEval(state, { q: {
1192
- type: "choice",
1193
- instructions: question,
1194
- criteria
1195
- } }, token, endpoint);
1196
- if (!r.ok) return {
1197
- ok: false,
1198
- error: r.error
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;
1199
1123
  };
1200
- const a = r.answers.q ?? {};
1201
- if (typeof a.choice !== "string" || !a.probabilities) return {
1202
- ok: false,
1203
- error: "malformed answer: no choice/probabilities"
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;
1204
1195
  };
1205
- return {
1206
- ok: true,
1207
- choice: a.choice,
1208
- probabilities: a.probabilities,
1209
- usage: r.usage
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;
1210
1235
  };
1211
- }
1212
- /** Tri-state on the EXPECTED answer's probability — same thresholds and
1213
- * gray-band philosophy as boolean P(true). */
1214
- function mapChoiceOutcome(r, answer, high = .7, low = .4) {
1215
- if (!r.ok || !r.probabilities) return {
1216
- outcome: "pending",
1217
- reason: r.error || "no probabilities"
1218
- };
1219
- const p = r.probabilities[answer];
1220
- if (typeof p !== "number") return {
1221
- outcome: "pending",
1222
- reason: `expected answer '${answer}' not in distribution`
1223
- };
1224
- const base = {
1225
- p,
1226
- 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;
1227
1291
  };
1228
- if (p >= high) return {
1229
- outcome: "pass",
1230
- ...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;
1231
1338
  };
1232
- if (p <= low) return {
1233
- outcome: "fail",
1234
- ...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;
1235
1390
  };
1236
- return {
1237
- outcome: "pending",
1238
- ...base,
1239
- 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
1240
1460
  };
1461
+ return ds;
1241
1462
  }
1242
- async function evalScore(state, question, levels, token, endpoint = GATEWAY_URL) {
1243
- if (!token) return {
1244
- ok: false,
1245
- error: NO_TOKEN
1246
- };
1247
- const r = await postEval(state, { q: {
1248
- type: "score",
1249
- instructions: question,
1250
- criteria: levels
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
+ }
1472
+ }
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/database/src/plugin.ts
1481
+ /**
1482
+ * DatabasePlugin — provides ctx.database (DatabaseService).
1483
+ * This is the entry point that apps compose via ctx.plugin().
1484
+ *
1485
+ * @module @ygl/core-database/plugin
1486
+ */
1487
+ var DatabaseService = class extends _deepseek_ai_cordis.Service {
1488
+ db;
1489
+ disposed = false;
1490
+ constructor(ctx, dbFile) {
1491
+ super(ctx, "database");
1492
+ const lock = process.env.YGL_TEST !== "1" && process.env.YGL_SERVE !== "1";
1493
+ this.db = new DB(dbFile, { lock });
1494
+ }
1495
+ /** Lifecycle: Cordis calls this when the Context is disposed — the
1496
+ * "Services stop with Context, resources auto-clean" promise made real.
1497
+ * Closes the SQLite handle AND the cached ORM DataSource. */
1498
+ stop() {
1499
+ if (this.disposed) return;
1500
+ this.disposed = true;
1501
+ try {
1502
+ const { closeOrm } = (init_orm(), require_rolldown_runtime.__toCommonJS(orm_exports));
1503
+ closeOrm().catch(() => {});
1504
+ } catch {}
1505
+ try {
1506
+ this.db.close();
1507
+ } catch {}
1508
+ }
1509
+ };
1510
+ function apply$3(ctx, config) {
1511
+ new DatabaseService(ctx, config.file);
1512
+ }
1513
+ //#endregion
1514
+ //#region packages/web/app/src/index.ts
1515
+ /**
1516
+ * WebAppPlugin — provides ctx.webApp: serves the built React client
1517
+ * (apps/web/dist, workspace knowledge — never user config) over the
1518
+ * dashboard server. Pattern follows DSH packages/bundle/web-app: the app
1519
+ * entry builds with vite, this runtime-glue plugin resolves the dist and
1520
+ * hands static-file serving to whoever owns the HTTP surface.
1521
+ *
1522
+ * @module @ygl/web-app
1523
+ */
1524
+ /** Resolve the built client dist (workspace knowledge). Pure: safe to call
1525
+ * from short-lived CLI processes without betting on async plugin timing.
1526
+ *
1527
+ * Probe by REPO MARKER, not by counting levels from __dirname: under the
1528
+ * tsdown single-file build __dirname collapses to dist/bundle, so any
1529
+ * fixed-depth relative path misses. Walking up to the repo root (the dir
1530
+ * holding ygl.bundle.json) works from every layout — tsc tree, bundle,
1531
+ * and a run straight from src/. */
1532
+ function resolveDistRoot(from = __dirname) {
1533
+ const candidates = [];
1534
+ let dir = from;
1535
+ for (let i = 0; i < 6; i++, dir = node_path.dirname(dir)) {
1536
+ candidates.push(node_path.join(dir, "apps", "web", "dist"));
1537
+ if (node_fs.existsSync(node_path.join(dir, "ygl.bundle.json"))) break;
1538
+ }
1539
+ candidates.push(node_path.resolve(process.cwd(), "apps", "web", "dist"));
1540
+ for (const candidate of candidates) if (node_fs.existsSync(node_path.join(candidate, "index.html"))) return candidate;
1541
+ return null;
1542
+ }
1543
+ const MIME = {
1544
+ ".html": "text/html; charset=utf-8",
1545
+ ".js": "text/javascript; charset=utf-8",
1546
+ ".mjs": "text/javascript; charset=utf-8",
1547
+ ".css": "text/css; charset=utf-8",
1548
+ ".json": "application/json",
1549
+ ".svg": "image/svg+xml",
1550
+ ".png": "image/png",
1551
+ ".ico": "image/x-icon",
1552
+ ".woff2": "font/woff2",
1553
+ ".map": "application/json"
1554
+ };
1555
+ /** URL → dist 静态文件(SPA 回落 index.html;路径遍历安全)。纯函数。 */
1556
+ function openStatic(urlPath) {
1557
+ const root = resolveDistRoot();
1558
+ if (!root) return null;
1559
+ const clean = urlPath.split("?")[0].replace(/^\/+/, "");
1560
+ let file = node_path.resolve(root, clean === "" ? "index.html" : clean);
1561
+ if (!file.startsWith(root)) file = node_path.join(root, "index.html");
1562
+ if (!node_fs.existsSync(file) || node_fs.statSync(file).isDirectory()) {
1563
+ file = node_path.join(root, "index.html");
1564
+ if (!node_fs.existsSync(file)) return null;
1565
+ }
1566
+ const ext = node_path.extname(file).toLowerCase();
1567
+ return {
1568
+ body: node_fs.readFileSync(file),
1569
+ mime: MIME[ext] || "application/octet-stream"
1570
+ };
1571
+ }
1572
+ var WebAppService = class extends _deepseek_ai_cordis.Service {
1573
+ constructor(ctx) {
1574
+ super(ctx, "webApp");
1575
+ }
1576
+ /** Whether a built client exists to serve. */
1577
+ available() {
1578
+ return resolveDistRoot() !== null;
1579
+ }
1580
+ /** Delegates to the pure resolver — short-lived CLI callers may use the
1581
+ * standalone openStatic() without waiting on plugin timing. */
1582
+ open(urlPath) {
1583
+ return openStatic(urlPath);
1584
+ }
1585
+ };
1586
+ function apply$2(ctx) {
1587
+ new WebAppService(ctx);
1588
+ }
1589
+ //#endregion
1590
+ //#region packages/techtree/eval/src/eval.ts
1591
+ /**
1592
+ * Decision-model boolean evaluator — spec R8 (docs/spec.md).
1593
+ *
1594
+ * Speaks the Vercel AI Gateway v4 evaluation-model protocol (TypeSafe jev):
1595
+ * deterministic P(true) judgments, 1-2s/call, native CJK, collapses toward
1596
+ * the gray band instead of hard-answering on thin evidence. Material:
1597
+ * F:/Users/Administrator/Downloads/vercel-ai-gateway-eval-api.md.
1598
+ *
1599
+ * Offline-safe by design: callers map "no token / network failure" to a
1600
+ * PENDING gate outcome (never a hard fail) — the evaluator is an
1601
+ * enhancement, not a dependency.
1602
+ */
1603
+ /** Normalize a configured api_base: a bare host gets the standard v4
1604
+ * evaluation-model path appended; a full endpoint is used as-is.
1605
+ * Config priority is composed by the CALLER (cmds layer):
1606
+ * config(decision_model_llm_api_base) > this default. */
1607
+ function resolveEndpoint(base) {
1608
+ const v = String(base || "").trim().replace(/\/+$/, "");
1609
+ if (!v) return GATEWAY_URL;
1610
+ if (/\/v4\/ai\/evaluation-model$/.test(v)) return v;
1611
+ return `${v}/v4/ai/evaluation-model`;
1612
+ }
1613
+ /** Token resolution: env first, then ~/.vercel.ai-gateway.env (token=...).
1614
+ * Set YGL_EVAL_OFFLINE=1 to force the offline path (tests/demos on
1615
+ * machines that DO have a token file — keeps them deterministic). */
1616
+ function evalToken() {
1617
+ if (process.env.YGL_EVAL_OFFLINE) return "";
1618
+ if (process.env.AI_GATEWAY_API_KEY) return process.env.AI_GATEWAY_API_KEY;
1619
+ try {
1620
+ const m = /^token\s*=\s*(\S+)/m.exec(node_fs.readFileSync(node_path.join(node_os.homedir(), ".vercel.ai-gateway.env"), "utf8"));
1621
+ if (m) return m[1];
1622
+ } catch {}
1623
+ return "";
1624
+ }
1625
+ /** One boolean question about a state. Retries 429/529 with exponential
1626
+ * backoff (<=3); 400/401 and malformed answers return without retry;
1627
+ * 60s timeout per attempt. `endpoint` defaults to the Vercel gateway —
1628
+ * callers pass the config-resolved value (decision_model_llm_api_base
1629
+ * has priority over the default). */
1630
+ async function evalBoolean(state, question, token, endpoint = GATEWAY_URL) {
1631
+ if (!token) return {
1632
+ ok: false,
1633
+ error: NO_TOKEN
1634
+ };
1635
+ const r = await postEval(state, { q: {
1636
+ type: "boolean",
1637
+ instructions: question
1251
1638
  } }, token, endpoint);
1252
1639
  if (!r.ok) return {
1253
1640
  ok: false,
1254
1641
  error: r.error
1255
1642
  };
1256
- const a = r.answers.q ?? {};
1257
- if (typeof a.score !== "number" || !a.probabilities) return {
1643
+ const p = r.answers.q?.probability;
1644
+ if (typeof p !== "number") return {
1258
1645
  ok: false,
1259
- error: "malformed answer: no score/probabilities"
1646
+ error: "malformed answer: no boolean probability"
1260
1647
  };
1261
1648
  return {
1262
1649
  ok: true,
1263
- score: a.score,
1264
- probabilities: a.probabilities,
1650
+ p,
1265
1651
  usage: r.usage
1266
1652
  };
1267
1653
  }
1268
- /** Tri-state on the ordinal scale: score >= min → pass; score <= min-1 →
1269
- * fail; the ±1 band around the threshold is the gray zone (the score
1270
- * counterpart of boolean's (0.4, 0.7) process state). */
1271
- function mapScoreOutcome(r, min) {
1272
- if (!r.ok || r.score === void 0) return {
1273
- outcome: "pending",
1274
- reason: r.error || "no score"
1275
- };
1276
- const s = r.score;
1277
- if (s >= min) return {
1278
- outcome: "pass",
1279
- score: s
1280
- };
1281
- if (s <= min - 1) return {
1282
- outcome: "fail",
1283
- score: s
1284
- };
1285
- return {
1286
- outcome: "pending",
1287
- score: s,
1288
- reason: `gray band [${min - 1}, ${min}) around expected level ${min}`
1289
- };
1654
+ function parseEvalSpec(value) {
1655
+ const s = String(value ?? "").trim();
1656
+ if (s.startsWith("{")) try {
1657
+ const j = JSON.parse(s);
1658
+ if (j && typeof j.q === "string") return j;
1659
+ } catch {}
1660
+ return { q: s };
1290
1661
  }
1291
- async function evalMixed(state, questions, token, endpoint = GATEWAY_URL) {
1292
- if (!questions.length) return {
1293
- ok: true,
1294
- results: []
1295
- };
1296
- if (!token) return {
1297
- ok: false,
1298
- error: NO_TOKEN
1299
- };
1300
- const qmap = {};
1301
- questions.forEach((q, i) => {
1302
- if (q.type === "boolean") qmap[`q${i}`] = {
1303
- type: "boolean",
1304
- instructions: q.question
1305
- };
1306
- else if (q.type === "choice") {
1307
- const criteria = {};
1308
- for (const [k, v] of Object.entries(q.options)) criteria[k] = v || null;
1309
- qmap[`q${i}`] = {
1310
- type: "choice",
1311
- instructions: q.question,
1312
- criteria
1313
- };
1314
- } else qmap[`q${i}`] = {
1315
- type: "score",
1316
- instructions: q.question,
1317
- criteria: q.levels
1318
- };
1662
+ function packEvalChoiceSpec(q, options, answer) {
1663
+ return JSON.stringify({
1664
+ q,
1665
+ options,
1666
+ answer
1319
1667
  });
1320
- const r = await postEval(state, qmap, token, endpoint);
1321
- if (!r.ok) return {
1322
- ok: false,
1323
- error: r.error
1324
- };
1325
- const results = [];
1326
- for (let i = 0; i < questions.length; i++) {
1327
- const a = r.answers[`q${i}`] ?? {};
1328
- const t = questions[i].type;
1329
- if (t === "boolean") {
1330
- if (typeof a.probability !== "number") return {
1331
- ok: false,
1332
- error: `malformed answer q${i}: no boolean probability`
1333
- };
1334
- results.push({
1335
- ok: true,
1336
- type: "boolean",
1337
- p: a.probability
1338
- });
1339
- } else if (t === "choice") {
1668
+ }
1669
+ function packEvalScoreSpec(q, levels, min) {
1670
+ return JSON.stringify({
1671
+ q,
1672
+ levels,
1673
+ min
1674
+ });
1675
+ }
1676
+ async function postEval(state, questions, token, endpoint) {
1677
+ const url = resolveEndpoint(endpoint);
1678
+ const body = JSON.stringify({
1679
+ state,
1680
+ questions
1681
+ });
1682
+ for (let attempt = 0;; attempt++) try {
1683
+ const res = await fetch(url, {
1684
+ method: "POST",
1685
+ headers: {
1686
+ Authorization: `Bearer ${token}`,
1687
+ "Content-Type": "application/json",
1688
+ "ai-model-id": MODEL_ID,
1689
+ "ai-evaluation-model-specification-version": "4",
1690
+ "ai-gateway-protocol-version": "0.0.1",
1691
+ "ai-gateway-auth-method": "api-key"
1692
+ },
1693
+ body,
1694
+ signal: AbortSignal.timeout(6e4)
1695
+ });
1696
+ if (res.status === 429 || res.status === 529) {
1697
+ if (attempt >= 3) return {
1698
+ ok: false,
1699
+ error: `HTTP ${res.status} after ${attempt + 1} attempts`
1700
+ };
1701
+ await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
1702
+ continue;
1703
+ }
1704
+ if (!res.ok) return {
1705
+ ok: false,
1706
+ error: `HTTP ${res.status} (protocol/auth error — no retry)`
1707
+ };
1708
+ const j = await res.json();
1709
+ if (!j?.answers) return {
1710
+ ok: false,
1711
+ error: "malformed response: no answers"
1712
+ };
1713
+ return {
1714
+ ok: true,
1715
+ answers: j.answers,
1716
+ usage: j.usage ?? void 0
1717
+ };
1718
+ } catch (e) {
1719
+ const msg = String(e?.message || e);
1720
+ if (attempt < 3 && /timeout|abort|network|fetch failed|ECONN/i.test(msg)) {
1721
+ await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
1722
+ continue;
1723
+ }
1724
+ return {
1725
+ ok: false,
1726
+ error: msg
1727
+ };
1728
+ }
1729
+ }
1730
+ async function evalChoice(state, question, options, token, endpoint = GATEWAY_URL) {
1731
+ if (!token) return {
1732
+ ok: false,
1733
+ error: NO_TOKEN
1734
+ };
1735
+ const criteria = {};
1736
+ for (const [k, v] of Object.entries(options)) criteria[k] = v || null;
1737
+ const r = await postEval(state, { q: {
1738
+ type: "choice",
1739
+ instructions: question,
1740
+ criteria
1741
+ } }, token, endpoint);
1742
+ if (!r.ok) return {
1743
+ ok: false,
1744
+ error: r.error
1745
+ };
1746
+ const a = r.answers.q ?? {};
1747
+ if (typeof a.choice !== "string" || !a.probabilities) return {
1748
+ ok: false,
1749
+ error: "malformed answer: no choice/probabilities"
1750
+ };
1751
+ return {
1752
+ ok: true,
1753
+ choice: a.choice,
1754
+ probabilities: a.probabilities,
1755
+ usage: r.usage
1756
+ };
1757
+ }
1758
+ /** Tri-state on the EXPECTED answer's probability — same thresholds and
1759
+ * gray-band philosophy as boolean P(true). */
1760
+ function mapChoiceOutcome(r, answer, high = .7, low = .4) {
1761
+ if (!r.ok || !r.probabilities) return {
1762
+ outcome: "pending",
1763
+ reason: r.error || "no probabilities"
1764
+ };
1765
+ const p = r.probabilities[answer];
1766
+ if (typeof p !== "number") return {
1767
+ outcome: "pending",
1768
+ reason: `expected answer '${answer}' not in distribution`
1769
+ };
1770
+ const base = {
1771
+ p,
1772
+ choice: r.choice
1773
+ };
1774
+ if (p >= high) return {
1775
+ outcome: "pass",
1776
+ ...base
1777
+ };
1778
+ if (p <= low) return {
1779
+ outcome: "fail",
1780
+ ...base
1781
+ };
1782
+ return {
1783
+ outcome: "pending",
1784
+ ...base,
1785
+ reason: `gray band ${low}<P<${high} (process state)`
1786
+ };
1787
+ }
1788
+ async function evalScore(state, question, levels, token, endpoint = GATEWAY_URL) {
1789
+ if (!token) return {
1790
+ ok: false,
1791
+ error: NO_TOKEN
1792
+ };
1793
+ const r = await postEval(state, { q: {
1794
+ type: "score",
1795
+ instructions: question,
1796
+ criteria: levels
1797
+ } }, token, endpoint);
1798
+ if (!r.ok) return {
1799
+ ok: false,
1800
+ error: r.error
1801
+ };
1802
+ const a = r.answers.q ?? {};
1803
+ if (typeof a.score !== "number" || !a.probabilities) return {
1804
+ ok: false,
1805
+ error: "malformed answer: no score/probabilities"
1806
+ };
1807
+ return {
1808
+ ok: true,
1809
+ score: a.score,
1810
+ probabilities: a.probabilities,
1811
+ usage: r.usage
1812
+ };
1813
+ }
1814
+ /** Tri-state on the ordinal scale: score >= min → pass; score <= min-1 →
1815
+ * fail; the ±1 band around the threshold is the gray zone (the score
1816
+ * counterpart of boolean's (0.4, 0.7) process state). */
1817
+ function mapScoreOutcome(r, min) {
1818
+ if (!r.ok || r.score === void 0) return {
1819
+ outcome: "pending",
1820
+ reason: r.error || "no score"
1821
+ };
1822
+ const s = r.score;
1823
+ if (s >= min) return {
1824
+ outcome: "pass",
1825
+ score: s
1826
+ };
1827
+ if (s <= min - 1) return {
1828
+ outcome: "fail",
1829
+ score: s
1830
+ };
1831
+ return {
1832
+ outcome: "pending",
1833
+ score: s,
1834
+ reason: `gray band [${min - 1}, ${min}) around expected level ${min}`
1835
+ };
1836
+ }
1837
+ async function evalMixed(state, questions, token, endpoint = GATEWAY_URL) {
1838
+ if (!questions.length) return {
1839
+ ok: true,
1840
+ results: []
1841
+ };
1842
+ if (!token) return {
1843
+ ok: false,
1844
+ error: NO_TOKEN
1845
+ };
1846
+ const qmap = {};
1847
+ questions.forEach((q, i) => {
1848
+ if (q.type === "boolean") qmap[`q${i}`] = {
1849
+ type: "boolean",
1850
+ instructions: q.question
1851
+ };
1852
+ else if (q.type === "choice") {
1853
+ const criteria = {};
1854
+ for (const [k, v] of Object.entries(q.options)) criteria[k] = v || null;
1855
+ qmap[`q${i}`] = {
1856
+ type: "choice",
1857
+ instructions: q.question,
1858
+ criteria
1859
+ };
1860
+ } else qmap[`q${i}`] = {
1861
+ type: "score",
1862
+ instructions: q.question,
1863
+ criteria: q.levels
1864
+ };
1865
+ });
1866
+ const r = await postEval(state, qmap, token, endpoint);
1867
+ if (!r.ok) return {
1868
+ ok: false,
1869
+ error: r.error
1870
+ };
1871
+ const results = [];
1872
+ for (let i = 0; i < questions.length; i++) {
1873
+ const a = r.answers[`q${i}`] ?? {};
1874
+ const t = questions[i].type;
1875
+ if (t === "boolean") {
1876
+ if (typeof a.probability !== "number") return {
1877
+ ok: false,
1878
+ error: `malformed answer q${i}: no boolean probability`
1879
+ };
1880
+ results.push({
1881
+ ok: true,
1882
+ type: "boolean",
1883
+ p: a.probability
1884
+ });
1885
+ } else if (t === "choice") {
1340
1886
  if (typeof a.choice !== "string" || !a.probabilities) return {
1341
1887
  ok: false,
1342
1888
  error: `malformed answer q${i}: no choice/probabilities`
@@ -3257,695 +3803,229 @@ CREATE TABLE artifacts (
3257
3803
  version integer NOT NULL DEFAULT 1, created text NOT NULL,
3258
3804
  meta text NOT NULL DEFAULT '');
3259
3805
  CREATE TABLE kb_notes (
3260
- id text PRIMARY KEY, title text NOT NULL, body text NOT NULL DEFAULT '',
3261
- source_url text DEFAULT '', origin text DEFAULT '', tags text DEFAULT '',
3262
- confidence text NOT NULL DEFAULT 'B', reuse integer NOT NULL DEFAULT 0,
3263
- content_hash text NOT NULL DEFAULT '', created text NOT NULL, updated text NOT NULL,
3264
- UNIQUE (content_hash));
3265
- CREATE TABLE kb_links (
3266
- from_id text NOT NULL REFERENCES kb_notes(id) ON DELETE CASCADE,
3267
- to_id text NOT NULL REFERENCES kb_notes(id) ON DELETE CASCADE,
3268
- rel text NOT NULL DEFAULT 'related', PRIMARY KEY (from_id, to_id, rel));
3269
- CREATE TABLE trees (
3270
- id text PRIMARY KEY, project_id text REFERENCES projects(id) ON DELETE CASCADE,
3271
- name text NOT NULL UNIQUE, note text DEFAULT '', created text NOT NULL);
3272
- CREATE TABLE nodes (
3273
- id text PRIMARY KEY, tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
3274
- key text NOT NULL, name text NOT NULL, kind text NOT NULL DEFAULT 'tech',
3275
- tier integer NOT NULL DEFAULT 0, cost double precision NOT NULL DEFAULT 0,
3276
- cost_unit text DEFAULT '', payload text DEFAULT '', tags text DEFAULT '',
3277
- created text NOT NULL, UNIQUE (tree_id, key));
3278
- CREATE TABLE node_edges (
3279
- id text PRIMARY KEY, tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
3280
- parent_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3281
- child_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3282
- rel text NOT NULL DEFAULT 'prereq', mode text NOT NULL DEFAULT 'AND',
3283
- required integer NOT NULL DEFAULT 1, min_level integer NOT NULL DEFAULT 1,
3284
- weight double precision NOT NULL DEFAULT 1, note text DEFAULT '',
3285
- UNIQUE (parent_id, child_id, rel));
3286
- CREATE TABLE node_closure (
3287
- tree_id text NOT NULL, ancestor text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3288
- descendant text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3289
- depth integer NOT NULL, PRIMARY KEY (ancestor, descendant));
3290
- CREATE TABLE node_conditions (
3291
- id text PRIMARY KEY, node_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3292
- name text NOT NULL, kind text NOT NULL DEFAULT 'flag', expr text NOT NULL DEFAULT '',
3293
- op text NOT NULL DEFAULT '>=', value text NOT NULL DEFAULT '',
3294
- required integer NOT NULL DEFAULT 1, effect text NOT NULL DEFAULT 'gate',
3295
- weight double precision NOT NULL DEFAULT 1, note text DEFAULT '');
3296
- CREATE TABLE node_state (
3297
- tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
3298
- context text NOT NULL DEFAULT 'default', node_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3299
- status text NOT NULL DEFAULT 'locked', level integer NOT NULL DEFAULT 0,
3300
- progress double precision NOT NULL DEFAULT 0, updated text NOT NULL,
3301
- PRIMARY KEY (tree_id, context, node_id));
3302
- CREATE TABLE node_index (
3303
- tree_id text PRIMARY KEY REFERENCES trees(id) ON DELETE CASCADE,
3304
- topo_order text NOT NULL DEFAULT '', tiers text NOT NULL DEFAULT '',
3305
- in_degree text NOT NULL DEFAULT '', built_at text NOT NULL, dirty integer NOT NULL DEFAULT 1);
3306
- CREATE TABLE relations (
3307
- id text PRIMARY KEY, src_type text NOT NULL, src_id text NOT NULL,
3308
- dst_type text NOT NULL, dst_id text NOT NULL, rel text NOT NULL DEFAULT 'related',
3309
- weight double precision NOT NULL DEFAULT 1,
3310
- UNIQUE (src_type, src_id, dst_type, dst_id, rel));
3311
- CREATE TABLE jobs (
3312
- id text PRIMARY KEY, title text NOT NULL, command text NOT NULL,
3313
- every_minutes integer DEFAULT 0, at text DEFAULT '', enabled integer NOT NULL DEFAULT 1,
3314
- runs integer NOT NULL DEFAULT 0, last_run text DEFAULT '',
3315
- status text NOT NULL DEFAULT 'pending', created text NOT NULL);
3316
- CREATE TABLE runs (
3317
- id text PRIMARY KEY, job_id text NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
3318
- started text NOT NULL, finished text DEFAULT '', rc integer, output text DEFAULT '');
3319
- CREATE TABLE events (
3320
- id text PRIMARY KEY, ts text NOT NULL, actor text NOT NULL DEFAULT 'cli', kind text NOT NULL,
3321
- entity text DEFAULT '', entity_id text DEFAULT '', payload text DEFAULT '',
3322
- level text NOT NULL DEFAULT 'info');
3323
- `;
3324
- const MIGRATE_INDEXES = `
3325
- CREATE INDEX IF NOT EXISTS idx_sources_project ON sources(project_id);
3326
- CREATE INDEX IF NOT EXISTS idx_evidence_project ON evidence(project_id);
3327
- CREATE INDEX IF NOT EXISTS idx_claims_project ON claims(project_id);
3328
- CREATE INDEX IF NOT EXISTS idx_artifacts_proj ON artifacts(project_id);
3329
- CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts);
3330
- CREATE INDEX IF NOT EXISTS idx_jobs_enabled ON jobs(enabled);
3331
- CREATE INDEX IF NOT EXISTS idx_nodes_tree ON nodes(tree_id);
3332
- CREATE INDEX IF NOT EXISTS idx_nodes_tier ON nodes(tree_id, tier);
3333
- CREATE INDEX IF NOT EXISTS idx_edge_child ON node_edges(child_id);
3334
- CREATE INDEX IF NOT EXISTS idx_edge_parent ON node_edges(parent_id);
3335
- CREATE INDEX IF NOT EXISTS idx_edge_req ON node_edges(child_id, required);
3336
- CREATE INDEX IF NOT EXISTS idx_closure_desc ON node_closure(descendant);
3337
- CREATE INDEX IF NOT EXISTS idx_closure_anc ON node_closure(ancestor);
3338
- CREATE INDEX IF NOT EXISTS idx_cond_node ON node_conditions(node_id);
3339
- CREATE INDEX IF NOT EXISTS idx_cond_req ON node_conditions(node_id, required);
3340
- CREATE INDEX IF NOT EXISTS idx_state_ctx ON node_state(tree_id, context);
3341
- CREATE INDEX IF NOT EXISTS idx_state_status ON node_state(tree_id, context, status);
3342
- `;
3343
- /**
3344
- * One-time migration of the whole library into a standalone PG database.
3345
- * Atomic (single transaction: DDL + data + verification), refuses a non-
3346
- * empty target unless `fresh` drops the existing tables first. After
3347
- * migration the CLI keeps working on SQLite only — no dual writes.
3348
- */
3349
- async function migrateToPg(db, url, fresh = false) {
3350
- let pg;
3351
- try {
3352
- pg = require("pg");
3353
- } catch {
3354
- throw new Error("pg driver not installed — run: pnpm add pg (optional dependency)");
3355
- }
3356
- const typeErrors = preflightTypeCheck(db);
3357
- if (typeErrors.length) {
3358
- const lines = typeErrors.slice(0, 5).map((e) => ` ${e.table}.${e.column} rowid=${e.rowid}: value "${e.value}" is not a valid INTEGER`);
3359
- 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>"`);
3360
- }
3361
- const client = new pg.Client({ connectionString: url });
3362
- await client.connect();
3363
- try {
3364
- const verNum = Number((await client.query("SHOW server_version_num")).rows[0].server_version_num);
3365
- const ver = (await client.query("SELECT version()")).rows[0].version;
3366
- const fulltext = verNum >= 12e4 ? "tsvector+GIN" : "ngram-only(pg<12)";
3367
- if (!fresh) {
3368
- for (const t of MIRROR_TABLES) if ((await client.query(`SELECT 1 AS hit FROM information_schema.tables WHERE table_name=$1`, [t])).rowCount) {
3369
- const n = (await client.query(`SELECT count(*)::int n FROM "${t}"`)).rows[0].n;
3370
- 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`);
3371
- }
3372
- } else for (const t of [...MIRROR_TABLES].reverse()) await client.query(`DROP TABLE IF EXISTS "${t}" CASCADE`);
3373
- await client.query("BEGIN");
3374
- try {
3375
- await client.query(MIGRATE_DDL);
3376
- await client.query(MIGRATE_INDEXES);
3377
- for (const t of Object.keys(NGRAM_INPUT)) {
3378
- await client.query(`ALTER TABLE "${t}" ADD COLUMN IF NOT EXISTS ngram text NOT NULL DEFAULT ''`);
3379
- if (verNum >= 12e4) {
3380
- await client.query(`ALTER TABLE "${t}" ADD COLUMN IF NOT EXISTS ngram_tsv tsvector
3381
- GENERATED ALWAYS AS (to_tsvector('simple', regexp_replace(coalesce(ngram,''), '[/\\._:,&+()]+', ' ', 'g'))) STORED`);
3382
- await client.query(`CREATE INDEX IF NOT EXISTS "${t}_tsv" ON "${t}" USING GIN (ngram_tsv)`);
3383
- }
3384
- }
3385
- for (const t of MIRROR_TABLES) {
3386
- const src = db.all(`PRAGMA table_info(${t})`).map((c) => c.name).sort();
3387
- const dst = (await client.query(`SELECT column_name FROM information_schema.columns WHERE table_name=$1`, [t])).rows.map((r) => r.column_name);
3388
- const want = new Set(src);
3389
- if (Object.keys(NGRAM_INPUT).includes(t)) {
3390
- want.add("ngram");
3391
- if (verNum >= 12e4) want.add("ngram_tsv");
3392
- }
3393
- const got = new Set(dst);
3394
- 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`);
3395
- }
3396
- const result = [];
3397
- for (const t of MIRROR_TABLES) {
3398
- const rows = await copyTable(client, db, t);
3399
- const n = (await client.query(`SELECT count(*)::int n FROM "${t}"`)).rows[0].n;
3400
- if (n !== rows) throw new Error(`count mismatch on ${t}: copied ${rows}, found ${n}`);
3401
- result.push({
3402
- table: t,
3403
- rows
3404
- });
3405
- }
3406
- await client.query("COMMIT");
3407
- return {
3408
- tables: result,
3409
- server_version: ver,
3410
- constraints: {
3411
- fk: (await client.query(`SELECT count(*)::int n FROM information_schema.table_constraints WHERE constraint_type='FOREIGN KEY'`)).rows[0].n,
3412
- unique: (await client.query(`SELECT count(*)::int n FROM information_schema.table_constraints WHERE constraint_type='UNIQUE'`)).rows[0].n
3413
- },
3414
- fulltext
3415
- };
3416
- } catch (e) {
3417
- await client.query("ROLLBACK");
3418
- throw e;
3419
- }
3420
- } finally {
3421
- await client.end();
3422
- }
3423
- }
3424
- /** Pre-flight: scan INTEGER columns for non-integer values (SQLite weak
3425
- * typing allows REAL into INTEGER; PG rejects at migration time with no
3426
- * context). Returns actionable errors with table.column+rowid. */
3427
- function preflightTypeCheck(db) {
3428
- const issues = [];
3429
- 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'");
3430
- for (const t of tables) {
3431
- const cols = db.all(`PRAGMA table_info(${t.name})`);
3432
- for (const c of cols) {
3433
- if (c.type !== "INTEGER") continue;
3434
- const bad = db.all(`SELECT rowid, ${c.name} AS v FROM ${t.name} WHERE typeof(${c.name}) NOT IN ('integer','null') LIMIT 5`);
3435
- for (const row of bad) issues.push({
3436
- table: t.name,
3437
- column: c.name,
3438
- rowid: row.rowid,
3439
- value: String(row.v)
3440
- });
3441
- }
3442
- }
3443
- return issues;
3444
- }
3445
- //#endregion
3446
- //#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorateMetadata.js
3447
- function __decorateMetadata(k, v) {
3448
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
3449
- }
3450
- var init_decorateMetadata = require_rolldown_runtime.__esmMin((() => {}));
3451
- //#endregion
3452
- //#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorate.js
3453
- function __decorate(decorators, target, key, desc) {
3454
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3455
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3456
- 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;
3457
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3458
- }
3459
- var init_decorate = require_rolldown_runtime.__esmMin((() => {}));
3460
- //#endregion
3461
- //#region packages/research/pipeline/src/entities.ts
3462
- /**
3463
- * Research-domain entities — a typed snapshot of schema v5.
3464
- *
3465
- * Contract with the schema authority:
3466
- * - `synchronize` is ALWAYS false; migrations stay on the PRAGMA
3467
- * user_version track (src/db.ts). If SCHEMA_SQL changes, these
3468
- * entities must be updated in the same commit.
3469
- * - Column names are explicit snake_case to match the database exactly.
3470
- * - FTS5 virtual tables and PRAGMA paths are OUT of entity scope; they
3471
- * stay raw SQL in store.ts (see docs/plan.md TypeORM boundaries).
3472
- */
3473
- var Project, Query, Source, QuerySource, Evidence, Claim, ClaimEvidence, RESEARCH_ENTITIES;
3474
- var init_entities$1 = require_rolldown_runtime.__esmMin((() => {
3475
- init_decorateMetadata();
3476
- init_decorate();
3477
- Project = class Project {
3478
- id;
3479
- name;
3480
- question;
3481
- inclusion;
3482
- exclusion;
3483
- status;
3484
- created;
3485
- updated;
3486
- queries;
3487
- claims;
3488
- sources;
3489
- };
3490
- __decorate([(0, typeorm.PrimaryColumn)({
3491
- name: "id",
3492
- type: "text"
3493
- }), __decorateMetadata("design:type", String)], Project.prototype, "id", void 0);
3494
- __decorate([(0, typeorm.Column)({
3495
- name: "name",
3496
- type: "text"
3497
- }), __decorateMetadata("design:type", String)], Project.prototype, "name", void 0);
3498
- __decorate([(0, typeorm.Column)({
3499
- name: "question",
3500
- type: "text"
3501
- }), __decorateMetadata("design:type", String)], Project.prototype, "question", void 0);
3502
- __decorate([(0, typeorm.Column)({
3503
- name: "inclusion",
3504
- type: "text"
3505
- }), __decorateMetadata("design:type", String)], Project.prototype, "inclusion", void 0);
3506
- __decorate([(0, typeorm.Column)({
3507
- name: "exclusion",
3508
- type: "text"
3509
- }), __decorateMetadata("design:type", String)], Project.prototype, "exclusion", void 0);
3510
- __decorate([(0, typeorm.Column)({
3511
- name: "status",
3512
- type: "text"
3513
- }), __decorateMetadata("design:type", String)], Project.prototype, "status", void 0);
3514
- __decorate([(0, typeorm.Column)({
3515
- name: "created",
3516
- type: "text"
3517
- }), __decorateMetadata("design:type", String)], Project.prototype, "created", void 0);
3518
- __decorate([(0, typeorm.Column)({
3519
- name: "updated",
3520
- type: "text"
3521
- }), __decorateMetadata("design:type", String)], Project.prototype, "updated", void 0);
3522
- __decorate([(0, typeorm.OneToMany)(() => Query, (q) => q.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "queries", void 0);
3523
- __decorate([(0, typeorm.OneToMany)(() => Claim, (c) => c.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "claims", void 0);
3524
- __decorate([(0, typeorm.OneToMany)(() => Source, (s) => s.project, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Project.prototype, "sources", void 0);
3525
- Project = __decorate([(0, typeorm.Entity)({ name: "projects" })], Project);
3526
- Query = class Query {
3527
- id;
3528
- projectId;
3529
- text;
3530
- engine;
3531
- status;
3532
- note;
3533
- contentHash;
3534
- created;
3535
- project;
3536
- querySources;
3537
- };
3538
- __decorate([(0, typeorm.PrimaryColumn)({
3539
- name: "id",
3540
- type: "text"
3541
- }), __decorateMetadata("design:type", String)], Query.prototype, "id", void 0);
3542
- __decorate([(0, typeorm.Column)({
3543
- name: "project_id",
3544
- type: "text"
3545
- }), __decorateMetadata("design:type", String)], Query.prototype, "projectId", void 0);
3546
- __decorate([(0, typeorm.Column)({
3547
- name: "text",
3548
- type: "text"
3549
- }), __decorateMetadata("design:type", String)], Query.prototype, "text", void 0);
3550
- __decorate([(0, typeorm.Column)({
3551
- name: "engine",
3552
- type: "text"
3553
- }), __decorateMetadata("design:type", String)], Query.prototype, "engine", void 0);
3554
- __decorate([(0, typeorm.Column)({
3555
- name: "status",
3556
- type: "text"
3557
- }), __decorateMetadata("design:type", String)], Query.prototype, "status", void 0);
3558
- __decorate([(0, typeorm.Column)({
3559
- name: "note",
3560
- type: "text"
3561
- }), __decorateMetadata("design:type", String)], Query.prototype, "note", void 0);
3562
- __decorate([(0, typeorm.Column)({
3563
- name: "content_hash",
3564
- type: "text"
3565
- }), __decorateMetadata("design:type", String)], Query.prototype, "contentHash", void 0);
3566
- __decorate([(0, typeorm.Column)({
3567
- name: "created",
3568
- type: "text"
3569
- }), __decorateMetadata("design:type", String)], Query.prototype, "created", void 0);
3570
- __decorate([
3571
- (0, typeorm.ManyToOne)(() => Project, (p) => p.queries, { createForeignKeyConstraints: false }),
3572
- (0, typeorm.JoinColumn)({ name: "project_id" }),
3573
- __decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
3574
- ], Query.prototype, "project", void 0);
3575
- __decorate([(0, typeorm.OneToMany)(() => QuerySource, (qs) => qs.query, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Query.prototype, "querySources", void 0);
3576
- Query = __decorate([(0, typeorm.Entity)({ name: "queries" })], Query);
3577
- Source = class Source {
3578
- id;
3579
- projectId;
3580
- url;
3581
- title;
3582
- urlHash;
3583
- contentHash;
3584
- abstract;
3585
- content;
3586
- engine;
3587
- relevance;
3588
- confidence;
3589
- tags;
3590
- retrievedAt;
3591
- created;
3592
- project;
3593
- querySources;
3594
- };
3595
- __decorate([(0, typeorm.PrimaryColumn)({
3596
- name: "id",
3597
- type: "text"
3598
- }), __decorateMetadata("design:type", String)], Source.prototype, "id", void 0);
3599
- __decorate([(0, typeorm.Column)({
3600
- name: "project_id",
3601
- type: "text"
3602
- }), __decorateMetadata("design:type", String)], Source.prototype, "projectId", void 0);
3603
- __decorate([(0, typeorm.Column)({
3604
- name: "url",
3605
- type: "text"
3606
- }), __decorateMetadata("design:type", String)], Source.prototype, "url", void 0);
3607
- __decorate([(0, typeorm.Column)({
3608
- name: "title",
3609
- type: "text"
3610
- }), __decorateMetadata("design:type", String)], Source.prototype, "title", void 0);
3611
- __decorate([(0, typeorm.Column)({
3612
- name: "url_hash",
3613
- type: "text"
3614
- }), __decorateMetadata("design:type", String)], Source.prototype, "urlHash", void 0);
3615
- __decorate([(0, typeorm.Column)({
3616
- name: "content_hash",
3617
- type: "text"
3618
- }), __decorateMetadata("design:type", String)], Source.prototype, "contentHash", void 0);
3619
- __decorate([(0, typeorm.Column)({
3620
- name: "abstract",
3621
- type: "text"
3622
- }), __decorateMetadata("design:type", String)], Source.prototype, "abstract", void 0);
3623
- __decorate([(0, typeorm.Column)({
3624
- name: "content",
3625
- type: "text"
3626
- }), __decorateMetadata("design:type", String)], Source.prototype, "content", void 0);
3627
- __decorate([(0, typeorm.Column)({
3628
- name: "engine",
3629
- type: "text"
3630
- }), __decorateMetadata("design:type", String)], Source.prototype, "engine", void 0);
3631
- __decorate([(0, typeorm.Column)({
3632
- name: "relevance",
3633
- type: "integer"
3634
- }), __decorateMetadata("design:type", Number)], Source.prototype, "relevance", void 0);
3635
- __decorate([(0, typeorm.Column)({
3636
- name: "confidence",
3637
- type: "text"
3638
- }), __decorateMetadata("design:type", String)], Source.prototype, "confidence", void 0);
3639
- __decorate([(0, typeorm.Column)({
3640
- name: "tags",
3641
- type: "text"
3642
- }), __decorateMetadata("design:type", String)], Source.prototype, "tags", void 0);
3643
- __decorate([(0, typeorm.Column)({
3644
- name: "retrieved_at",
3645
- type: "text"
3646
- }), __decorateMetadata("design:type", String)], Source.prototype, "retrievedAt", void 0);
3647
- __decorate([(0, typeorm.Column)({
3648
- name: "created",
3649
- type: "text",
3650
- nullable: true
3651
- }), __decorateMetadata("design:type", String)], Source.prototype, "created", void 0);
3652
- __decorate([
3653
- (0, typeorm.ManyToOne)(() => Project, (p) => p.sources, { createForeignKeyConstraints: false }),
3654
- (0, typeorm.JoinColumn)({ name: "project_id" }),
3655
- __decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
3656
- ], Source.prototype, "project", void 0);
3657
- __decorate([(0, typeorm.OneToMany)(() => QuerySource, (qs) => qs.source, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Source.prototype, "querySources", void 0);
3658
- Source = __decorate([(0, typeorm.Entity)({ name: "sources" })], Source);
3659
- QuerySource = class QuerySource {
3660
- queryId;
3661
- sourceId;
3662
- note;
3663
- created;
3664
- query;
3665
- source;
3666
- };
3667
- __decorate([(0, typeorm.PrimaryColumn)({
3668
- name: "query_id",
3669
- type: "text"
3670
- }), __decorateMetadata("design:type", String)], QuerySource.prototype, "queryId", void 0);
3671
- __decorate([(0, typeorm.PrimaryColumn)({
3672
- name: "source_id",
3673
- type: "text"
3674
- }), __decorateMetadata("design:type", String)], QuerySource.prototype, "sourceId", void 0);
3675
- __decorate([(0, typeorm.Column)({
3676
- name: "note",
3677
- type: "text"
3678
- }), __decorateMetadata("design:type", String)], QuerySource.prototype, "note", void 0);
3679
- __decorate([(0, typeorm.Column)({
3680
- name: "created",
3681
- type: "text"
3682
- }), __decorateMetadata("design:type", String)], QuerySource.prototype, "created", void 0);
3683
- __decorate([
3684
- (0, typeorm.ManyToOne)(() => Query, (q) => q.querySources, { createForeignKeyConstraints: false }),
3685
- (0, typeorm.JoinColumn)({ name: "query_id" }),
3686
- __decorateMetadata("design:type", typeof Query === "undefined" ? Object : Query)
3687
- ], QuerySource.prototype, "query", void 0);
3688
- __decorate([
3689
- (0, typeorm.ManyToOne)(() => Source, (s) => s.querySources, { createForeignKeyConstraints: false }),
3690
- (0, typeorm.JoinColumn)({ name: "source_id" }),
3691
- __decorateMetadata("design:type", typeof Source === "undefined" ? Object : Source)
3692
- ], QuerySource.prototype, "source", void 0);
3693
- QuerySource = __decorate([(0, typeorm.Entity)({ name: "query_sources" })], QuerySource);
3694
- Evidence = class Evidence {
3695
- id;
3696
- projectId;
3697
- sourceId;
3698
- quote;
3699
- note;
3700
- locator;
3701
- contentHash;
3702
- created;
3703
- project;
3704
- source;
3705
- claimEvidence;
3706
- };
3707
- __decorate([(0, typeorm.PrimaryColumn)({
3708
- name: "id",
3709
- type: "text"
3710
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "id", void 0);
3711
- __decorate([(0, typeorm.Column)({
3712
- name: "project_id",
3713
- type: "text"
3714
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "projectId", void 0);
3715
- __decorate([(0, typeorm.Column)({
3716
- name: "source_id",
3717
- type: "text"
3718
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "sourceId", void 0);
3719
- __decorate([(0, typeorm.Column)({
3720
- name: "quote",
3721
- type: "text"
3722
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "quote", void 0);
3723
- __decorate([(0, typeorm.Column)({
3724
- name: "note",
3725
- type: "text"
3726
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "note", void 0);
3727
- __decorate([(0, typeorm.Column)({
3728
- name: "locator",
3729
- type: "text"
3730
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "locator", void 0);
3731
- __decorate([(0, typeorm.Column)({
3732
- name: "content_hash",
3733
- type: "text"
3734
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "contentHash", void 0);
3735
- __decorate([(0, typeorm.Column)({
3736
- name: "created",
3737
- type: "text"
3738
- }), __decorateMetadata("design:type", String)], Evidence.prototype, "created", void 0);
3739
- __decorate([
3740
- (0, typeorm.ManyToOne)(() => Project, void 0, { createForeignKeyConstraints: false }),
3741
- (0, typeorm.JoinColumn)({ name: "project_id" }),
3742
- __decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
3743
- ], Evidence.prototype, "project", void 0);
3744
- __decorate([
3745
- (0, typeorm.ManyToOne)(() => Source, void 0, { createForeignKeyConstraints: false }),
3746
- (0, typeorm.JoinColumn)({ name: "source_id" }),
3747
- __decorateMetadata("design:type", typeof Source === "undefined" ? Object : Source)
3748
- ], Evidence.prototype, "source", void 0);
3749
- __decorate([(0, typeorm.OneToMany)(() => ClaimEvidence, (ce) => ce.evidence, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Evidence.prototype, "claimEvidence", void 0);
3750
- Evidence = __decorate([(0, typeorm.Entity)({ name: "evidence" })], Evidence);
3751
- Claim = class Claim {
3752
- id;
3753
- projectId;
3754
- text;
3755
- status;
3756
- confidence;
3757
- contentHash;
3758
- created;
3759
- updated;
3760
- project;
3761
- claimEvidence;
3762
- };
3763
- __decorate([(0, typeorm.PrimaryColumn)({
3764
- name: "id",
3765
- type: "text"
3766
- }), __decorateMetadata("design:type", String)], Claim.prototype, "id", void 0);
3767
- __decorate([(0, typeorm.Column)({
3768
- name: "project_id",
3769
- type: "text"
3770
- }), __decorateMetadata("design:type", String)], Claim.prototype, "projectId", void 0);
3771
- __decorate([(0, typeorm.Column)({
3772
- name: "text",
3773
- type: "text"
3774
- }), __decorateMetadata("design:type", String)], Claim.prototype, "text", void 0);
3775
- __decorate([(0, typeorm.Column)({
3776
- name: "status",
3777
- type: "text"
3778
- }), __decorateMetadata("design:type", String)], Claim.prototype, "status", void 0);
3779
- __decorate([(0, typeorm.Column)({
3780
- name: "confidence",
3781
- type: "text"
3782
- }), __decorateMetadata("design:type", String)], Claim.prototype, "confidence", void 0);
3783
- __decorate([(0, typeorm.Column)({
3784
- name: "content_hash",
3785
- type: "text"
3786
- }), __decorateMetadata("design:type", String)], Claim.prototype, "contentHash", void 0);
3787
- __decorate([(0, typeorm.Column)({
3788
- name: "created",
3789
- type: "text"
3790
- }), __decorateMetadata("design:type", String)], Claim.prototype, "created", void 0);
3791
- __decorate([(0, typeorm.Column)({
3792
- name: "updated",
3793
- type: "text",
3794
- nullable: true
3795
- }), __decorateMetadata("design:type", String)], Claim.prototype, "updated", void 0);
3796
- __decorate([
3797
- (0, typeorm.ManyToOne)(() => Project, (p) => p.claims, { createForeignKeyConstraints: false }),
3798
- (0, typeorm.JoinColumn)({ name: "project_id" }),
3799
- __decorateMetadata("design:type", typeof Project === "undefined" ? Object : Project)
3800
- ], Claim.prototype, "project", void 0);
3801
- __decorate([(0, typeorm.OneToMany)(() => ClaimEvidence, (ce) => ce.claim, { createForeignKeyConstraints: false }), __decorateMetadata("design:type", Array)], Claim.prototype, "claimEvidence", void 0);
3802
- Claim = __decorate([(0, typeorm.Entity)({ name: "claims" })], Claim);
3803
- ClaimEvidence = class ClaimEvidence {
3804
- claimId;
3805
- evidenceId;
3806
- stance;
3807
- claim;
3808
- evidence;
3809
- };
3810
- __decorate([(0, typeorm.PrimaryColumn)({
3811
- name: "claim_id",
3812
- type: "text"
3813
- }), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "claimId", void 0);
3814
- __decorate([(0, typeorm.PrimaryColumn)({
3815
- name: "evidence_id",
3816
- type: "text"
3817
- }), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "evidenceId", void 0);
3818
- __decorate([(0, typeorm.Column)({
3819
- name: "stance",
3820
- type: "text"
3821
- }), __decorateMetadata("design:type", String)], ClaimEvidence.prototype, "stance", void 0);
3822
- __decorate([
3823
- (0, typeorm.ManyToOne)(() => Claim, (c) => c.claimEvidence, { createForeignKeyConstraints: false }),
3824
- (0, typeorm.JoinColumn)({ name: "claim_id" }),
3825
- __decorateMetadata("design:type", typeof Claim === "undefined" ? Object : Claim)
3826
- ], ClaimEvidence.prototype, "claim", void 0);
3827
- __decorate([
3828
- (0, typeorm.ManyToOne)(() => Evidence, void 0, { createForeignKeyConstraints: false }),
3829
- (0, typeorm.JoinColumn)({ name: "evidence_id" }),
3830
- __decorateMetadata("design:type", typeof Evidence === "undefined" ? Object : Evidence)
3831
- ], ClaimEvidence.prototype, "evidence", void 0);
3832
- ClaimEvidence = __decorate([(0, typeorm.Entity)({ name: "claim_evidence" })], ClaimEvidence);
3833
- RESEARCH_ENTITIES = [
3834
- Project,
3835
- Query,
3836
- Source,
3837
- QuerySource,
3838
- Evidence,
3839
- Claim,
3840
- ClaimEvidence
3841
- ];
3842
- }));
3843
- //#endregion
3844
- //#region packages/core/database/src/entities.ts
3845
- /** Cross-cutting entities (audit trail). Research-domain entities live in
3846
- * research.ts; both are registered in src/orm.ts. */
3847
- var Event, CORE_ENTITIES;
3848
- var init_entities = require_rolldown_runtime.__esmMin((() => {
3849
- init_decorateMetadata();
3850
- init_decorate();
3851
- Event = class Event {
3852
- id;
3853
- ts;
3854
- actor;
3855
- kind;
3856
- entity;
3857
- entityId;
3858
- payload;
3859
- /** v7 telemetry severity: info | warn | error (spec R10.2) */
3860
- level;
3861
- };
3862
- __decorate([(0, typeorm.PrimaryColumn)({
3863
- name: "id",
3864
- type: "text"
3865
- }), __decorateMetadata("design:type", String)], Event.prototype, "id", void 0);
3866
- __decorate([(0, typeorm.Column)({
3867
- name: "ts",
3868
- type: "text"
3869
- }), __decorateMetadata("design:type", String)], Event.prototype, "ts", void 0);
3870
- __decorate([(0, typeorm.Column)({
3871
- name: "actor",
3872
- type: "text"
3873
- }), __decorateMetadata("design:type", String)], Event.prototype, "actor", void 0);
3874
- __decorate([(0, typeorm.Column)({
3875
- name: "kind",
3876
- type: "text"
3877
- }), __decorateMetadata("design:type", String)], Event.prototype, "kind", void 0);
3878
- __decorate([(0, typeorm.Column)({
3879
- name: "entity",
3880
- type: "text"
3881
- }), __decorateMetadata("design:type", String)], Event.prototype, "entity", void 0);
3882
- __decorate([(0, typeorm.Column)({
3883
- name: "entity_id",
3884
- type: "text"
3885
- }), __decorateMetadata("design:type", String)], Event.prototype, "entityId", void 0);
3886
- __decorate([(0, typeorm.Column)({
3887
- name: "payload",
3888
- type: "text"
3889
- }), __decorateMetadata("design:type", String)], Event.prototype, "payload", void 0);
3890
- __decorate([(0, typeorm.Column)({
3891
- name: "level",
3892
- type: "text",
3893
- default: "info"
3894
- }), __decorateMetadata("design:type", String)], Event.prototype, "level", void 0);
3895
- Event = __decorate([(0, typeorm.Entity)({ name: "events" })], Event);
3896
- CORE_ENTITIES = [Event];
3897
- }));
3898
- //#endregion
3899
- //#region packages/core/database/src/orm.ts
3806
+ id text PRIMARY KEY, title text NOT NULL, body text NOT NULL DEFAULT '',
3807
+ source_url text DEFAULT '', origin text DEFAULT '', tags text DEFAULT '',
3808
+ confidence text NOT NULL DEFAULT 'B', reuse integer NOT NULL DEFAULT 0,
3809
+ content_hash text NOT NULL DEFAULT '', created text NOT NULL, updated text NOT NULL,
3810
+ UNIQUE (content_hash));
3811
+ CREATE TABLE kb_links (
3812
+ from_id text NOT NULL REFERENCES kb_notes(id) ON DELETE CASCADE,
3813
+ to_id text NOT NULL REFERENCES kb_notes(id) ON DELETE CASCADE,
3814
+ rel text NOT NULL DEFAULT 'related', PRIMARY KEY (from_id, to_id, rel));
3815
+ CREATE TABLE trees (
3816
+ id text PRIMARY KEY, project_id text REFERENCES projects(id) ON DELETE CASCADE,
3817
+ name text NOT NULL UNIQUE, note text DEFAULT '', created text NOT NULL);
3818
+ CREATE TABLE nodes (
3819
+ id text PRIMARY KEY, tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
3820
+ key text NOT NULL, name text NOT NULL, kind text NOT NULL DEFAULT 'tech',
3821
+ tier integer NOT NULL DEFAULT 0, cost double precision NOT NULL DEFAULT 0,
3822
+ cost_unit text DEFAULT '', payload text DEFAULT '', tags text DEFAULT '',
3823
+ created text NOT NULL, UNIQUE (tree_id, key));
3824
+ CREATE TABLE node_edges (
3825
+ id text PRIMARY KEY, tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
3826
+ parent_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3827
+ child_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3828
+ rel text NOT NULL DEFAULT 'prereq', mode text NOT NULL DEFAULT 'AND',
3829
+ required integer NOT NULL DEFAULT 1, min_level integer NOT NULL DEFAULT 1,
3830
+ weight double precision NOT NULL DEFAULT 1, note text DEFAULT '',
3831
+ UNIQUE (parent_id, child_id, rel));
3832
+ CREATE TABLE node_closure (
3833
+ tree_id text NOT NULL, ancestor text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3834
+ descendant text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3835
+ depth integer NOT NULL, PRIMARY KEY (ancestor, descendant));
3836
+ CREATE TABLE node_conditions (
3837
+ id text PRIMARY KEY, node_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3838
+ name text NOT NULL, kind text NOT NULL DEFAULT 'flag', expr text NOT NULL DEFAULT '',
3839
+ op text NOT NULL DEFAULT '>=', value text NOT NULL DEFAULT '',
3840
+ required integer NOT NULL DEFAULT 1, effect text NOT NULL DEFAULT 'gate',
3841
+ weight double precision NOT NULL DEFAULT 1, note text DEFAULT '');
3842
+ CREATE TABLE node_state (
3843
+ tree_id text NOT NULL REFERENCES trees(id) ON DELETE CASCADE,
3844
+ context text NOT NULL DEFAULT 'default', node_id text NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
3845
+ status text NOT NULL DEFAULT 'locked', level integer NOT NULL DEFAULT 0,
3846
+ progress double precision NOT NULL DEFAULT 0, updated text NOT NULL,
3847
+ PRIMARY KEY (tree_id, context, node_id));
3848
+ CREATE TABLE node_index (
3849
+ tree_id text PRIMARY KEY REFERENCES trees(id) ON DELETE CASCADE,
3850
+ topo_order text NOT NULL DEFAULT '', tiers text NOT NULL DEFAULT '',
3851
+ in_degree text NOT NULL DEFAULT '', built_at text NOT NULL, dirty integer NOT NULL DEFAULT 1);
3852
+ CREATE TABLE relations (
3853
+ id text PRIMARY KEY, src_type text NOT NULL, src_id text NOT NULL,
3854
+ dst_type text NOT NULL, dst_id text NOT NULL, rel text NOT NULL DEFAULT 'related',
3855
+ weight double precision NOT NULL DEFAULT 1,
3856
+ UNIQUE (src_type, src_id, dst_type, dst_id, rel));
3857
+ CREATE TABLE jobs (
3858
+ id text PRIMARY KEY, title text NOT NULL, command text NOT NULL,
3859
+ every_minutes integer DEFAULT 0, at text DEFAULT '', enabled integer NOT NULL DEFAULT 1,
3860
+ runs integer NOT NULL DEFAULT 0, last_run text DEFAULT '',
3861
+ status text NOT NULL DEFAULT 'pending', created text NOT NULL);
3862
+ CREATE TABLE runs (
3863
+ id text PRIMARY KEY, job_id text NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
3864
+ started text NOT NULL, finished text DEFAULT '', rc integer, output text DEFAULT '');
3865
+ CREATE TABLE events (
3866
+ id text PRIMARY KEY, ts text NOT NULL, actor text NOT NULL DEFAULT 'cli', kind text NOT NULL,
3867
+ entity text DEFAULT '', entity_id text DEFAULT '', payload text DEFAULT '',
3868
+ level text NOT NULL DEFAULT 'info');
3869
+ `;
3870
+ const MIGRATE_INDEXES = `
3871
+ CREATE INDEX IF NOT EXISTS idx_sources_project ON sources(project_id);
3872
+ CREATE INDEX IF NOT EXISTS idx_evidence_project ON evidence(project_id);
3873
+ CREATE INDEX IF NOT EXISTS idx_claims_project ON claims(project_id);
3874
+ CREATE INDEX IF NOT EXISTS idx_artifacts_proj ON artifacts(project_id);
3875
+ CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts);
3876
+ CREATE INDEX IF NOT EXISTS idx_jobs_enabled ON jobs(enabled);
3877
+ CREATE INDEX IF NOT EXISTS idx_nodes_tree ON nodes(tree_id);
3878
+ CREATE INDEX IF NOT EXISTS idx_nodes_tier ON nodes(tree_id, tier);
3879
+ CREATE INDEX IF NOT EXISTS idx_edge_child ON node_edges(child_id);
3880
+ CREATE INDEX IF NOT EXISTS idx_edge_parent ON node_edges(parent_id);
3881
+ CREATE INDEX IF NOT EXISTS idx_edge_req ON node_edges(child_id, required);
3882
+ CREATE INDEX IF NOT EXISTS idx_closure_desc ON node_closure(descendant);
3883
+ CREATE INDEX IF NOT EXISTS idx_closure_anc ON node_closure(ancestor);
3884
+ CREATE INDEX IF NOT EXISTS idx_cond_node ON node_conditions(node_id);
3885
+ CREATE INDEX IF NOT EXISTS idx_cond_req ON node_conditions(node_id, required);
3886
+ CREATE INDEX IF NOT EXISTS idx_state_ctx ON node_state(tree_id, context);
3887
+ CREATE INDEX IF NOT EXISTS idx_state_status ON node_state(tree_id, context, status);
3888
+ `;
3900
3889
  /**
3901
- * Lazy TypeORM DataSource over the SAME SQLite file the CLI works on.
3902
- *
3903
- * Boundaries (see docs/plan.md "TypeORM boundaries"):
3904
- * - `synchronize` is permanently false — schema authority stays with
3905
- * SCHEMA_SQL + PRAGMA user_version migrations in src/db.ts. Entities in
3906
- * src/entities/ are a typed snapshot; change them in the same commit as
3907
- * SCHEMA_SQL.
3908
- * - TypeORM is a QUERY layer here, not a migration layer. FTS5 MATCH /
3909
- * bm25 loops, PRAGMA paths and the PG sync/migrate machinery stay raw
3910
- * SQL on the DB class.
3911
- * - CLI commands using the ORM must return a Promise (main() awaits) so
3912
- * the DataSource settles before the wasm DB closes.
3890
+ * One-time migration of the whole library into a standalone PG database.
3891
+ * Atomic (single transaction: DDL + data + verification), refuses a non-
3892
+ * empty target unless `fresh` drops the existing tables first. After
3893
+ * migration the CLI keeps working on SQLite only — no dual writes.
3913
3894
  */
3914
- var orm_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
3915
- closeOrm: () => closeOrm,
3916
- getOrm: () => getOrm
3917
- });
3918
- async function getOrm(dbFile) {
3919
- if (cached && cached.file === dbFile) return cached.ds;
3920
- const ds = new typeorm.DataSource({
3921
- type: "better-sqlite3",
3922
- database: dbFile,
3923
- synchronize: false,
3924
- entities: [...RESEARCH_ENTITIES, ...CORE_ENTITIES]
3925
- });
3926
- await ds.initialize();
3927
- await ds.query("PRAGMA synchronous=NORMAL");
3928
- cached = {
3929
- file: dbFile,
3930
- ds
3931
- };
3932
- return ds;
3933
- }
3934
- /** Destroy the cached DataSource (tests/scratch dirs cannot be removed on
3935
- * Windows while a better-sqlite3 handle keeps the file open — EPERM). */
3936
- async function closeOrm() {
3937
- if (cached) {
3895
+ async function migrateToPg(db, url, fresh = false) {
3896
+ let pg;
3897
+ try {
3898
+ pg = require("pg");
3899
+ } catch {
3900
+ throw new Error("pg driver not installed — run: pnpm add pg (optional dependency)");
3901
+ }
3902
+ const typeErrors = preflightTypeCheck(db);
3903
+ if (typeErrors.length) {
3904
+ const lines = typeErrors.slice(0, 5).map((e) => ` ${e.table}.${e.column} rowid=${e.rowid}: value "${e.value}" is not a valid INTEGER`);
3905
+ 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>"`);
3906
+ }
3907
+ const client = new pg.Client({ connectionString: url });
3908
+ await client.connect();
3909
+ try {
3910
+ const verNum = Number((await client.query("SHOW server_version_num")).rows[0].server_version_num);
3911
+ const ver = (await client.query("SELECT version()")).rows[0].version;
3912
+ const fulltext = verNum >= 12e4 ? "tsvector+GIN" : "ngram-only(pg<12)";
3913
+ if (!fresh) {
3914
+ for (const t of MIRROR_TABLES) if ((await client.query(`SELECT 1 AS hit FROM information_schema.tables WHERE table_name=$1`, [t])).rowCount) {
3915
+ const n = (await client.query(`SELECT count(*)::int n FROM "${t}"`)).rows[0].n;
3916
+ 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`);
3917
+ }
3918
+ } else for (const t of [...MIRROR_TABLES].reverse()) await client.query(`DROP TABLE IF EXISTS "${t}" CASCADE`);
3919
+ await client.query("BEGIN");
3938
3920
  try {
3939
- await cached.ds.destroy();
3940
- } catch {}
3941
- cached = null;
3921
+ await client.query(MIGRATE_DDL);
3922
+ await client.query(MIGRATE_INDEXES);
3923
+ for (const t of Object.keys(NGRAM_INPUT)) {
3924
+ await client.query(`ALTER TABLE "${t}" ADD COLUMN IF NOT EXISTS ngram text NOT NULL DEFAULT ''`);
3925
+ if (verNum >= 12e4) {
3926
+ await client.query(`ALTER TABLE "${t}" ADD COLUMN IF NOT EXISTS ngram_tsv tsvector
3927
+ GENERATED ALWAYS AS (to_tsvector('simple', regexp_replace(coalesce(ngram,''), '[/\\._:,&+()]+', ' ', 'g'))) STORED`);
3928
+ await client.query(`CREATE INDEX IF NOT EXISTS "${t}_tsv" ON "${t}" USING GIN (ngram_tsv)`);
3929
+ }
3930
+ }
3931
+ for (const t of MIRROR_TABLES) {
3932
+ const src = db.all(`PRAGMA table_info(${t})`).map((c) => c.name).sort();
3933
+ const dst = (await client.query(`SELECT column_name FROM information_schema.columns WHERE table_name=$1`, [t])).rows.map((r) => r.column_name);
3934
+ const want = new Set(src);
3935
+ if (Object.keys(NGRAM_INPUT).includes(t)) {
3936
+ want.add("ngram");
3937
+ if (verNum >= 12e4) want.add("ngram_tsv");
3938
+ }
3939
+ const got = new Set(dst);
3940
+ 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`);
3941
+ }
3942
+ const result = [];
3943
+ for (const t of MIRROR_TABLES) {
3944
+ const rows = await copyTable(client, db, t);
3945
+ const n = (await client.query(`SELECT count(*)::int n FROM "${t}"`)).rows[0].n;
3946
+ if (n !== rows) throw new Error(`count mismatch on ${t}: copied ${rows}, found ${n}`);
3947
+ result.push({
3948
+ table: t,
3949
+ rows
3950
+ });
3951
+ }
3952
+ await client.query("COMMIT");
3953
+ return {
3954
+ tables: result,
3955
+ server_version: ver,
3956
+ constraints: {
3957
+ fk: (await client.query(`SELECT count(*)::int n FROM information_schema.table_constraints WHERE constraint_type='FOREIGN KEY'`)).rows[0].n,
3958
+ unique: (await client.query(`SELECT count(*)::int n FROM information_schema.table_constraints WHERE constraint_type='UNIQUE'`)).rows[0].n
3959
+ },
3960
+ fulltext
3961
+ };
3962
+ } catch (e) {
3963
+ await client.query("ROLLBACK");
3964
+ throw e;
3965
+ }
3966
+ } finally {
3967
+ await client.end();
3942
3968
  }
3943
3969
  }
3944
- var cached;
3945
- var init_orm = require_rolldown_runtime.__esmMin((() => {
3946
- init_entities$1();
3947
- init_entities();
3948
- cached = null;
3970
+ /** Pre-flight: scan INTEGER columns for non-integer values (SQLite weak
3971
+ * typing allows REAL into INTEGER; PG rejects at migration time with no
3972
+ * context). Returns actionable errors with table.column+rowid. */
3973
+ function preflightTypeCheck(db) {
3974
+ const issues = [];
3975
+ 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'");
3976
+ for (const t of tables) {
3977
+ const cols = db.all(`PRAGMA table_info(${t.name})`);
3978
+ for (const c of cols) {
3979
+ if (c.type !== "INTEGER") continue;
3980
+ const bad = db.all(`SELECT rowid, ${c.name} AS v FROM ${t.name} WHERE typeof(${c.name}) NOT IN ('integer','null') LIMIT 5`);
3981
+ for (const row of bad) issues.push({
3982
+ table: t.name,
3983
+ column: c.name,
3984
+ rowid: row.rowid,
3985
+ value: String(row.v)
3986
+ });
3987
+ }
3988
+ }
3989
+ return issues;
3990
+ }
3991
+ //#endregion
3992
+ //#region packages/core/text/src/eventbus.ts
3993
+ /**
3994
+ * Cross-domain event bus — connects logEvent writes to real-time consumers.
3995
+ *
3996
+ * Architecture: a process-wide EventEmitter singleton. Write services call
3997
+ * `emitEvent()` after logEvent persists to the events table; consumers
3998
+ * (webui SSE, user plugins, future watchers) subscribe with `onEvent()`.
3999
+ *
4000
+ * This makes "ctx.on reactivity" real without requiring a full Cordis
4001
+ * event system — a lightweight bridge that the webui and plugins can use.
4002
+ *
4003
+ * @module @ygl/core-text/eventbus
4004
+ */
4005
+ var eventbus_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
4006
+ emitEvent: () => emitEvent,
4007
+ onEvent: () => onEvent,
4008
+ onEventKind: () => onEventKind
4009
+ });
4010
+ /** Publish a cross-domain event (called after events table write). */
4011
+ function emitEvent(evt) {
4012
+ bus.emit("ygl", evt);
4013
+ bus.emit(`ygl:${evt.kind}`, evt);
4014
+ }
4015
+ /** Subscribe to all events. Returns an unsubscribe function. */
4016
+ function onEvent(handler) {
4017
+ bus.on("ygl", handler);
4018
+ return () => bus.off("ygl", handler);
4019
+ }
4020
+ /** Subscribe to a specific kind (e.g. "node.unlock"). Returns unsubscribe. */
4021
+ function onEventKind(kind, handler) {
4022
+ bus.on(`ygl:${kind}`, handler);
4023
+ return () => bus.off(`ygl:${kind}`, handler);
4024
+ }
4025
+ var bus;
4026
+ var init_eventbus = require_rolldown_runtime.__esmMin((() => {
4027
+ bus = new node_events.EventEmitter();
4028
+ bus.setMaxListeners(50);
3949
4029
  }));
3950
4030
  //#endregion
3951
4031
  //#region packages/research/pipeline/src/writes.ts
@@ -3988,6 +4068,16 @@ async function logEvent$1(mgr, kind, entity, entityId, payload, level = "info")
3988
4068
  payload: p,
3989
4069
  level
3990
4070
  });
4071
+ try {
4072
+ const { emitEvent } = (init_eventbus(), require_rolldown_runtime.__toCommonJS(eventbus_exports));
4073
+ emitEvent({
4074
+ kind,
4075
+ entity,
4076
+ entityId,
4077
+ payload: String(payload),
4078
+ ts: (/* @__PURE__ */ new Date()).toISOString()
4079
+ });
4080
+ } catch {}
3991
4081
  }
3992
4082
  async function queryAdd(ds, projectId, text, engine = "", force = false) {
3993
4083
  const hash = contentHash(text, force);
@@ -4587,6 +4677,7 @@ var init_writes = require_rolldown_runtime.__esmMin((() => {
4587
4677
  //#endregion
4588
4678
  //#region packages/ops/tools/src/db-cmds.ts
4589
4679
  var db_cmds_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$12 });
4680
+ init_fmt();
4590
4681
  init_db();
4591
4682
  /**
4592
4683
  * Cold-start: import the built-in skills that ship with this package, so a
@@ -4607,7 +4698,14 @@ async function autoImportBuiltinSkills(env, dbFile) {
4607
4698
  }
4608
4699
  }
4609
4700
  }
4610
- if (!skillsDir) return 0;
4701
+ if (!skillsDir) {
4702
+ const { out: outFn, paint: paintFn, C: Cf } = (init_fmt(), require_rolldown_runtime.__toCommonJS(fmt_exports));
4703
+ if (env.ctx && !env.ctx.json) {
4704
+ outFn(env.ctx, paintFn(Cf.yellow, " ! built-in skills: 0 found (skills/ not shipped or not in package)", env.ctx.color));
4705
+ outFn(env.ctx, paintFn(Cf.dim, " this is a packaging bug — skills/ should be in the npm tarball", env.ctx.color));
4706
+ }
4707
+ return 0;
4708
+ }
4611
4709
  const { getOrm } = (init_orm(), require_rolldown_runtime.__toCommonJS(orm_exports));
4612
4710
  const writes = (init_writes(), require_rolldown_runtime.__toCommonJS(writes_exports));
4613
4711
  const { parseFrontmatter } = (init_text(), require_rolldown_runtime.__toCommonJS(text_exports));
@@ -4645,6 +4743,7 @@ function run$12(env, action, rest, args) {
4645
4743
  ok(ctx, `database: ${file}`);
4646
4744
  autoImportBuiltinSkills(env, file).then(async (n) => {
4647
4745
  if (n > 0) ok(ctx, `built-in skills: ${n} imported (agent match ready)`);
4746
+ else warn(ctx, "built-in skills: 0 imported — skills/ directory not found in package");
4648
4747
  try {
4649
4748
  const { closeOrm } = (init_orm(), require_rolldown_runtime.__toCommonJS(orm_exports));
4650
4749
  await closeOrm();
@@ -5282,6 +5381,7 @@ function configReset(db, key) {
5282
5381
  //#endregion
5283
5382
  //#region packages/ops/tools/src/config-cmds.ts
5284
5383
  var config_cmds_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$11 });
5384
+ init_fmt();
5285
5385
  function run$11(env, action, rest, args) {
5286
5386
  const { ctx, db } = env;
5287
5387
  switch (action) {
@@ -5368,6 +5468,7 @@ function run$11(env, action, rest, args) {
5368
5468
  /** job group — batch + schedule. `job tick` is what cron/systemctl drives.
5369
5469
  * Writes (add) run on the TypeORM write service (src/writes.ts). */
5370
5470
  var job_cmds_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$10 });
5471
+ init_fmt();
5371
5472
  init_text();
5372
5473
  init_orm();
5373
5474
  init_writes();
@@ -5575,6 +5676,7 @@ var agent_cmds_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
5575
5676
  buildBrief: () => buildBrief,
5576
5677
  run: () => run$9
5577
5678
  });
5679
+ init_fmt();
5578
5680
  init_text();
5579
5681
  /** Build the brief payload (where am I / what exists / what next).
5580
5682
  * Shared by the `agent brief` command and the WebUI dashboard. */
@@ -5939,11 +6041,7 @@ function run$9(env, action, rest, args) {
5939
6041
  }
5940
6042
  //#endregion
5941
6043
  //#region packages/core/text/src/flagcheck.ts
5942
- /**
5943
- * Flag validation helpers (0.54): the cold-user audits found that bare flags
5944
- * (parser returns boolean true) were silently stored as "true" strings, and
5945
- * enum flags accepted arbitrary values. These helpers centralize the checks.
5946
- */
6044
+ init_fmt();
5947
6045
  /**
5948
6046
  * Reject bare flags (given without a value → parser yields boolean true).
5949
6047
  * Returns true if any flag failed (caller should `return 2`).
@@ -6121,6 +6219,7 @@ var init_eval_enhance = require_rolldown_runtime.__esmMin((() => {
6121
6219
  * `source fetch` is async (Node 18+ global fetch) and returns a promise.
6122
6220
  * List commands run on TypeORM (src/orm.ts); search stays raw SQL (FTS5). */
6123
6221
  var cmds_exports$2 = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$8 });
6222
+ init_fmt();
6124
6223
  init_text();
6125
6224
  init_orm();
6126
6225
  init_writes();
@@ -6697,6 +6796,7 @@ function run$8(env, action, rest, args) {
6697
6796
  //#endregion
6698
6797
  //#region packages/research/produce/src/project-cmds.ts
6699
6798
  var project_cmds_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$7 });
6799
+ init_fmt();
6700
6800
  init_orm();
6701
6801
  init_writes();
6702
6802
  init_entities$1();
@@ -6835,6 +6935,7 @@ function run$7(env, action, rest, args) {
6835
6935
  * Writes run on the TypeORM write service (src/writes.ts); reads stay on
6836
6936
  * Store until their own migration batch. */
6837
6937
  var produce_cmds_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$6 });
6938
+ init_fmt();
6838
6939
  init_text();
6839
6940
  init_orm();
6840
6941
  init_writes();
@@ -7151,6 +7252,16 @@ async function logEvent(mgr, kind, entityId, payload, level = "info") {
7151
7252
  body,
7152
7253
  level
7153
7254
  ]);
7255
+ try {
7256
+ const { emitEvent } = (init_eventbus(), require_rolldown_runtime.__toCommonJS(eventbus_exports));
7257
+ emitEvent({
7258
+ kind,
7259
+ entity: "node",
7260
+ entityId,
7261
+ payload: String(body),
7262
+ ts: (/* @__PURE__ */ new Date()).toISOString()
7263
+ });
7264
+ } catch {}
7154
7265
  }
7155
7266
  async function reaches(mgr, treeId, fromId, toId) {
7156
7267
  return (await mgr.query(`SELECT 1 AS hit FROM node_closure WHERE tree_id=? AND ancestor=? AND descendant=? AND depth>0`, [
@@ -7725,6 +7836,7 @@ async function importTree(ds, spec, opts = {}) {
7725
7836
  * ygl-trees node export [--out file] portable JSON
7726
7837
  */
7727
7838
  var cmds_exports$1 = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$5 });
7839
+ init_fmt();
7728
7840
  init_orm();
7729
7841
  init_eval();
7730
7842
  init_text();
@@ -8571,6 +8683,7 @@ function parseState(args) {
8571
8683
  /** skill group — the L1/L2/L3 registry agents query before acting.
8572
8684
  * Writes run on the TypeORM write service (src/writes.ts). */
8573
8685
  var skill_cmds_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$4 });
8686
+ init_fmt();
8574
8687
  init_text();
8575
8688
  init_orm();
8576
8689
  init_writes();
@@ -8888,6 +9001,7 @@ function wrap(text, indent = 4) {
8888
9001
  /** kb group — long-term knowledge: store, retrieve, connect, promote to skill.
8889
9002
  * Writes (add/ingest) run on the TypeORM write service (src/writes.ts). */
8890
9003
  var cmds_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run$3 });
9004
+ init_fmt();
8891
9005
  init_text();
8892
9006
  init_orm();
8893
9007
  init_writes();
@@ -9200,6 +9314,7 @@ var rels_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
9200
9314
  resolveRef: () => resolveRef,
9201
9315
  run: () => run$2
9202
9316
  });
9317
+ init_fmt();
9203
9318
  init_text();
9204
9319
  init_orm();
9205
9320
  const REL_TYPES = [
@@ -9504,6 +9619,7 @@ var webui_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
9504
9619
  nodeDetail: () => nodeDetail,
9505
9620
  run: () => run$1
9506
9621
  });
9622
+ init_fmt();
9507
9623
  init_orm();
9508
9624
  function run$1(env, action, rest, args) {
9509
9625
  if (action === "serve") return serve(env, args);
@@ -10319,6 +10435,15 @@ function createDashboardServer(env) {
10319
10435
  const h = setInterval(tick, 1500);
10320
10436
  if (typeof h.unref === "function") h.unref();
10321
10437
  }
10438
+ let unsubBus = null;
10439
+ try {
10440
+ const { onEvent } = (init_eventbus(), require_rolldown_runtime.__toCommonJS(eventbus_exports));
10441
+ unsubBus = onEvent((evt) => {
10442
+ if (!sseClients.size) return;
10443
+ push("data", gatherData(env));
10444
+ push(evt.kind.replace(/\//g, "-"), evt);
10445
+ });
10446
+ } catch {}
10322
10447
  const server = http.createServer((req, res) => {
10323
10448
  const u = new URL(req.url || "/", "http://127.0.0.1");
10324
10449
  const url = u.pathname;
@@ -10416,7 +10541,15 @@ function createDashboardServer(env) {
10416
10541
  sseClients.clear();
10417
10542
  };
10418
10543
  server.on("close", dropLive);
10419
- server.closeLive = dropLive;
10544
+ server.closeLive = () => {
10545
+ dropLive();
10546
+ if (unsubBus) {
10547
+ try {
10548
+ unsubBus();
10549
+ } catch {}
10550
+ unsubBus = null;
10551
+ }
10552
+ };
10420
10553
  return server;
10421
10554
  }
10422
10555
  /** Live dashboard: blocks until SIGINT/SIGTERM, then resolves so the CLI can
@@ -10475,6 +10608,7 @@ function serve(env, args) {
10475
10608
  //#endregion
10476
10609
  //#region packages/ops/tools/src/doctor.ts
10477
10610
  var doctor_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ run: () => run });
10611
+ init_fmt();
10478
10612
  init_eval();
10479
10613
  init_db();
10480
10614
  init_plugins();
@@ -10713,6 +10847,7 @@ const DOMAIN_MODULES = {
10713
10847
  * Composition happens in profile-boot; no domain logic here.
10714
10848
  */
10715
10849
  init_db();
10850
+ init_fmt();
10716
10851
  const HOME = process.env.YGL_HOME || node_path.join(node_os.homedir(), ".ygl-trees");
10717
10852
  const PLUGINS = {
10718
10853
  database: {
@@ -11174,6 +11309,9 @@ async function main(argv) {
11174
11309
  return 1;
11175
11310
  }).finally(() => {
11176
11311
  try {
11312
+ if (booted?.ctx?.database?.stop) booted.ctx.database.stop();
11313
+ } catch {}
11314
+ if (!booted?.ctx?.database) try {
11177
11315
  db?.close();
11178
11316
  } catch {}
11179
11317
  });