aiex-cli 0.1.0 → 0.1.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { A as package_default, C as DEFAULT_PROMPT_CONFIG, D as seedConfig, E as createConfig, O as description, S as DEFAULT_MINERU_CONFIG, T as PLACEHOLDER_TEXT, _ as doctorDiagnosticsSeverityRows, a as recognizeImageText, b as DEFAULT_LITEPARSE_CONFIG, c as t, d as writeAIConfig, f as AIConfigSchema, h as toSnakeCase, j as version, k as name, l as getDefaultAIConfig, m as parseJsonSchema, n as collectDoctorDiagnostics, o as shouldUseImageOcrFallback, p as JsonSchemaDefinitionSchema, r as createMigrationConfig, s as initI18n, t as generateDrizzleSchema, u as readAIConfig, v as doctorDiagnosticsTableRows, w as PLACEHOLDER_SCHEMA, x as DEFAULT_MINERU_API_CONFIG, y as formatDoctorDiagnosticsJson } from "./generate-drizzle-schema-BqSy0OMm.mjs";
1
+ import { A as package_default, C as DEFAULT_PROMPT_CONFIG, D as seedConfig, E as createConfig, O as description, S as DEFAULT_MINERU_CONFIG, T as PLACEHOLDER_TEXT, _ as doctorDiagnosticsSeverityRows, a as recognizeImageText, b as DEFAULT_LITEPARSE_CONFIG, c as t, d as writeAIConfig, f as AIConfigSchema, h as toSnakeCase, j as version, k as name, l as getDefaultAIConfig, m as parseJsonSchema, n as collectDoctorDiagnostics, o as shouldUseImageOcrFallback, p as JsonSchemaDefinitionSchema, r as createMigrationConfig, s as initI18n, t as generateDrizzleSchema, u as readAIConfig, v as doctorDiagnosticsTableRows, w as PLACEHOLDER_SCHEMA, x as DEFAULT_MINERU_API_CONFIG, y as formatDoctorDiagnosticsJson } from "./generate-drizzle-schema-sZ01QiYJ.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import fs from "node:fs/promises";
4
4
  import os from "node:os";
@@ -13,13 +13,12 @@ import { defineCommand, runMain } from "citty";
13
13
  import { consola } from "consola";
14
14
  import updateNotifier from "update-notifier";
15
15
  import CliTable3 from "cli-table3";
16
- import fs$1 from "node:fs";
17
16
  import { confirm, intro, isCancel, outro, select, spinner, text } from "@clack/prompts";
18
17
  import pc from "picocolors";
19
- import { Buffer } from "node:buffer";
20
- import * as XLSX from "xlsx";
18
+ import fs$1 from "node:fs";
21
19
  import { TextDecoder, promisify } from "node:util";
22
20
  import { fileTypeFromBuffer, fileTypeFromFile } from "file-type";
21
+ import { Buffer } from "node:buffer";
23
22
  import { glob, globSync } from "tinyglobby";
24
23
  import { extractText, getDocumentProxy, getMeta } from "unpdf";
25
24
  import AdmZip from "adm-zip";
@@ -144,260 +143,9 @@ const doctorCommand = defineCommand({
144
143
  }
145
144
  });
146
145
 
147
- //#endregion
148
- //#region src/application/export/export-manager.ts
149
- function formatRowsConformingToSchema(rows, columns, schema, format) {
150
- return rows.map((row) => {
151
- const newRow = {};
152
- columns.forEach((col) => {
153
- const colName = col.name;
154
- const val = row[colName];
155
- const type = (schema?.properties?.[colName])?.type || "";
156
- if (val === null || val === void 0) newRow[colName] = "";
157
- else if (type === "boolean") if (format === "xlsx") newRow[colName] = val === 1 || val === "1" || val === true;
158
- else newRow[colName] = val === 1 || val === "1" || val === true ? "true" : "false";
159
- else if (type === "number" || type === "integer") if (val === "") newRow[colName] = "";
160
- else {
161
- const num = Number(val);
162
- newRow[colName] = Number.isNaN(num) ? val : num;
163
- }
164
- else if (typeof val === "object") newRow[colName] = JSON.stringify(val);
165
- else {
166
- const dbType = (col.type || "").toLowerCase();
167
- if ((dbType.includes("int") || dbType.includes("real") || dbType.includes("num") || dbType.includes("double") || dbType.includes("float")) && typeof val === "string" && val !== "") {
168
- const num = Number(val);
169
- newRow[colName] = Number.isNaN(num) ? val : num;
170
- } else newRow[colName] = val;
171
- }
172
- });
173
- return newRow;
174
- });
175
- }
176
- function generateExportBuffer(tableName, formattedRows, columns, format) {
177
- const ws = XLSX.utils.json_to_sheet(formattedRows, { header: columns.map((col) => col.name) });
178
- if (format === "xlsx") {
179
- const wb = XLSX.utils.book_new();
180
- XLSX.utils.book_append_sheet(wb, ws, tableName.slice(0, 31));
181
- return XLSX.write(wb, {
182
- bookType: "xlsx",
183
- type: "buffer"
184
- });
185
- } else {
186
- const csv = XLSX.utils.sheet_to_csv(ws);
187
- return Buffer.from("" + csv, "utf8");
188
- }
189
- }
190
-
191
- //#endregion
192
- //#region src/application/schema/load-schema.ts
193
- const JSON_EXT_RE$1 = /\.json$/;
194
- async function loadSchema(config, schemaName) {
195
- const schemaPath = path.join(config.schemaPath, `${schemaName}.json`);
196
- try {
197
- const parsed = await readFile(schemaPath);
198
- return { schema: JsonSchemaDefinitionSchema.parse(parsed) };
199
- } catch (e) {
200
- if (e instanceof ZodError) return {
201
- schema: null,
202
- error: t("errors.schema.validationFailed", {
203
- name: `${schemaName}.json`,
204
- issues: e.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")
205
- })
206
- };
207
- if (e.code === "ENOENT") return {
208
- schema: null,
209
- error: t("errors.schema.cannotRead", { name: `${schemaName}.json` })
210
- };
211
- if (e instanceof SyntaxError) return {
212
- schema: null,
213
- error: t("errors.schema.invalidJson", { name: `${schemaName}.json` })
214
- };
215
- return {
216
- schema: null,
217
- error: String(e)
218
- };
219
- }
220
- }
221
- async function listSchemas(aiexDir) {
222
- try {
223
- const dir = path.join(aiexDir, "schema");
224
- return (await fs.readdir(dir)).filter((f) => f.endsWith(".json")).map((f) => f.replace(JSON_EXT_RE$1, "")).sort();
225
- } catch {
226
- return [];
227
- }
228
- }
229
-
230
- //#endregion
231
- //#region src/commands/utils.ts
232
- function failCommand(message) {
233
- if (message) consola.error(message);
234
- outro(t("common.failed"));
235
- process.exitCode = 1;
236
- }
237
-
238
- //#endregion
239
- //#region src/commands/dump.ts
240
- const dumpCommand = defineCommand({
241
- meta: {
242
- name: "dump",
243
- description: t("command.dump.description")
244
- },
245
- args: {
246
- table: {
247
- type: "string",
248
- alias: "t",
249
- description: t("command.dump.args.table")
250
- },
251
- schema: {
252
- type: "string",
253
- alias: "s",
254
- description: t("command.dump.args.schema")
255
- },
256
- format: {
257
- type: "string",
258
- alias: "f",
259
- description: t("command.dump.args.format")
260
- },
261
- output: {
262
- type: "string",
263
- alias: "o",
264
- description: t("command.dump.args.output")
265
- }
266
- },
267
- async run({ args }) {
268
- intro(pc.inverse(" aiex dump "));
269
- await initI18n();
270
- if (!args.table && !args.schema) {
271
- failCommand(t("command.dump.errors.tableOrSchemaRequired"));
272
- return;
273
- }
274
- const cwd = process.cwd();
275
- const config = createMigrationConfig(cwd);
276
- const schemaDir = config.schemaPath;
277
- let tableName = args.table || "";
278
- let schema = null;
279
- if (args.schema) {
280
- const schemaLoad = await loadSchema(config, args.schema);
281
- if (!schemaLoad.schema) {
282
- failCommand(schemaLoad.error || t("command.dump.errors.schemaNotFound", { name: args.schema }));
283
- return;
284
- }
285
- schema = schemaLoad.schema;
286
- const tName = schema.table?.name;
287
- if (!tName) {
288
- failCommand(t("command.dump.errors.noTableName", { name: args.schema }));
289
- return;
290
- }
291
- if (tableName && tableName !== tName) {
292
- failCommand(t("command.dump.errors.tableMismatch", {
293
- table: tableName,
294
- schemaTable: tName
295
- }));
296
- return;
297
- }
298
- tableName = tName;
299
- } else try {
300
- if (fs$1.existsSync(schemaDir)) {
301
- const files = fs$1.readdirSync(schemaDir).filter((f) => f.endsWith(".json"));
302
- for (const file of files) {
303
- const s$1 = await readFile(path.join(schemaDir, file));
304
- if (s$1.table?.name === tableName) {
305
- schema = s$1;
306
- break;
307
- }
308
- }
309
- }
310
- } catch {}
311
- let format = args.format?.toLowerCase();
312
- const outputPathArg = args.output;
313
- if (outputPathArg) {
314
- const ext = path.extname(outputPathArg).toLowerCase();
315
- if (!format) {
316
- if (ext === ".xlsx") format = "xlsx";
317
- else if (ext === ".csv") format = "csv";
318
- }
319
- }
320
- if (!format) format = "csv";
321
- if (format !== "csv" && format !== "xlsx") {
322
- failCommand(t("command.dump.errors.unsupportedFormat", { format }));
323
- return;
324
- }
325
- const resolvedOutput = outputPathArg ? path.resolve(outputPathArg) : path.resolve(cwd, `${tableName}.${format}`);
326
- if (!fs$1.existsSync(config.databasePath)) {
327
- failCommand(t("command.dump.errors.dbNotFound", {
328
- path: config.databasePath,
329
- cmd: "aiex schema"
330
- }));
331
- return;
332
- }
333
- const s = spinner();
334
- s.start(t("command.dump.loading", { name: tableName }));
335
- let columns = [];
336
- let rows = [];
337
- try {
338
- const db = new Database(config.databasePath, { readonly: true });
339
- if (!db.prepare(`
340
- select name from sqlite_master
341
- where type = 'table' and name = ?
342
- `).get(tableName)) {
343
- s.stop(t("command.dump.dbQueryFailed"));
344
- failCommand(t("command.dump.errors.tableNotFound", {
345
- name: tableName,
346
- cmd: "aiex schema"
347
- }));
348
- db.close();
349
- return;
350
- }
351
- columns = db.pragma(`table_info(${tableName})`);
352
- rows = db.prepare(`select * from ${tableName}`).all();
353
- db.close();
354
- } catch (error) {
355
- s.stop(t("command.dump.dbQueryFailed"));
356
- failCommand(error instanceof Error ? error.message : String(error));
357
- return;
358
- }
359
- if (rows.length === 0) {
360
- s.stop(t("command.dump.emptyTable"));
361
- consola.warn(t("command.dump.errors.tableEmpty", { name: tableName }));
362
- } else s.stop(t("command.dump.loaded", { count: rows.length }));
363
- const s2 = spinner();
364
- s2.start(t("command.dump.formatting"));
365
- let formattedRows;
366
- try {
367
- formattedRows = formatRowsConformingToSchema(rows, columns, schema, format);
368
- s2.stop(t("command.dump.formatted"));
369
- } catch (error) {
370
- s2.stop(t("command.dump.dbQueryFailed"));
371
- failCommand(error instanceof Error ? error.message : String(error));
372
- return;
373
- }
374
- const s3 = spinner();
375
- s3.start(t("command.dump.writing", {
376
- format: format.toUpperCase(),
377
- path: resolvedOutput
378
- }));
379
- try {
380
- const buffer = generateExportBuffer(tableName, formattedRows, columns, format);
381
- const outputDir = path.dirname(resolvedOutput);
382
- if (!fs$1.existsSync(outputDir)) fs$1.mkdirSync(outputDir, { recursive: true });
383
- fs$1.writeFileSync(resolvedOutput, buffer);
384
- s3.stop(t("command.dump.dumpCompleted"));
385
- consola.success(t("command.dump.successMsg", {
386
- count: rows.length,
387
- path: pc.cyan(resolvedOutput)
388
- }));
389
- } catch (error) {
390
- s3.stop(t("command.dump.fileWriteFailed"));
391
- failCommand(error instanceof Error ? error.message : String(error));
392
- return;
393
- }
394
- outro(t("common.done"));
395
- }
396
- });
397
-
398
146
  //#endregion
399
147
  //#region src/application/extraction/quality.ts
400
- function formatInputProcessing$1(input) {
148
+ function formatInputProcessing(input) {
401
149
  const handler = input.converter ? `${input.handler}(${input.converter})` : input.handler;
402
150
  return `${input.mime ?? input.kind} -> ${handler}`;
403
151
  }
@@ -1414,16 +1162,16 @@ function getFileHash(filePath) {
1414
1162
  //#endregion
1415
1163
  //#region src/domain/ai-extraction/evidence.ts
1416
1164
  const NUMERIC_RE = /^-?\d+(?:\.\d+)?$/;
1417
- function isRecord$2(value) {
1165
+ function isRecord$3(value) {
1418
1166
  return typeof value === "object" && value !== null && !Array.isArray(value);
1419
1167
  }
1420
1168
  function stripEvidence(data) {
1421
- if (!isRecord$2(data)) return { data };
1169
+ if (!isRecord$3(data)) return { data };
1422
1170
  const { _evidence, ...businessData } = data;
1423
- if (!isRecord$2(_evidence)) return { data: businessData };
1171
+ if (!isRecord$3(_evidence)) return { data: businessData };
1424
1172
  return {
1425
1173
  data: businessData,
1426
- rawEvidence: Object.fromEntries(Object.entries(_evidence).filter(([, value]) => isRecord$2(value)).map(([field, value]) => [field, value]))
1174
+ rawEvidence: Object.fromEntries(Object.entries(_evidence).filter(([, value]) => isRecord$3(value)).map(([field, value]) => [field, value]))
1427
1175
  };
1428
1176
  }
1429
1177
  function findExactUnique(text$1, quote) {
@@ -1455,7 +1203,7 @@ function quoteContainsValue(quote, value, property) {
1455
1203
  return false;
1456
1204
  }
1457
1205
  function verifyFieldEvidence(input) {
1458
- if (!input.text || !isRecord$2(input.data) || !input.rawEvidence) return void 0;
1206
+ if (!input.text || !isRecord$3(input.data) || !input.rawEvidence) return void 0;
1459
1207
  const verified = {};
1460
1208
  for (const [field, raw] of Object.entries(input.rawEvidence)) {
1461
1209
  const property = input.schema.properties[field];
@@ -1689,7 +1437,7 @@ function propertyToExtractionSchema(property) {
1689
1437
  }
1690
1438
  return { type: nullableType(property.type) };
1691
1439
  }
1692
- function isRecord$1(value) {
1440
+ function isRecord$2(value) {
1693
1441
  return typeof value === "object" && value !== null && !Array.isArray(value);
1694
1442
  }
1695
1443
  function schemaToExtractionOutputSchema(schema) {
@@ -1727,7 +1475,7 @@ function validatePropertyValue(path$1, property, value, issues) {
1727
1475
  }
1728
1476
  return;
1729
1477
  case "object":
1730
- if (!isRecord$1(value)) {
1478
+ if (!isRecord$2(value)) {
1731
1479
  issues.push(`${path$1}: expected object or null`);
1732
1480
  return;
1733
1481
  }
@@ -1750,7 +1498,7 @@ function validateProperties(basePath, properties, data, issues) {
1750
1498
  }
1751
1499
  }
1752
1500
  function validateExtractedData(schema, data) {
1753
- if (!isRecord$1(data)) return {
1501
+ if (!isRecord$2(data)) return {
1754
1502
  success: false,
1755
1503
  error: "Extracted data must be a JSON object."
1756
1504
  };
@@ -1771,11 +1519,11 @@ const EVIDENCE_INSTRUCTIONS = `Evidence requirements:
1771
1519
  - The quote must be an exact contiguous substring copied from the input text.
1772
1520
  - Do not invent offsets. Only provide quotes.
1773
1521
  - If no exact quote supports a field, omit that field from "_evidence".`;
1774
- function isRecord(value) {
1522
+ function isRecord$1(value) {
1775
1523
  return typeof value === "object" && value !== null && !Array.isArray(value);
1776
1524
  }
1777
1525
  function withEvidenceSchema(schema) {
1778
- const properties = isRecord(schema.properties) ? schema.properties : {};
1526
+ const properties = isRecord$1(schema.properties) ? schema.properties : {};
1779
1527
  const required = Array.isArray(schema.required) ? schema.required : [];
1780
1528
  return {
1781
1529
  ...schema,
@@ -2166,6 +1914,45 @@ Please output the corrected JSON object now:`;
2166
1914
  }
2167
1915
  }
2168
1916
 
1917
+ //#endregion
1918
+ //#region src/application/schema/load-schema.ts
1919
+ const JSON_EXT_RE$1 = /\.json$/;
1920
+ async function loadSchema(config, schemaName) {
1921
+ const schemaPath = path.join(config.schemaPath, `${schemaName}.json`);
1922
+ try {
1923
+ const parsed = await readFile(schemaPath);
1924
+ return { schema: JsonSchemaDefinitionSchema.parse(parsed) };
1925
+ } catch (e) {
1926
+ if (e instanceof ZodError) return {
1927
+ schema: null,
1928
+ error: t("errors.schema.validationFailed", {
1929
+ name: `${schemaName}.json`,
1930
+ issues: e.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")
1931
+ })
1932
+ };
1933
+ if (e.code === "ENOENT") return {
1934
+ schema: null,
1935
+ error: t("errors.schema.cannotRead", { name: `${schemaName}.json` })
1936
+ };
1937
+ if (e instanceof SyntaxError) return {
1938
+ schema: null,
1939
+ error: t("errors.schema.invalidJson", { name: `${schemaName}.json` })
1940
+ };
1941
+ return {
1942
+ schema: null,
1943
+ error: String(e)
1944
+ };
1945
+ }
1946
+ }
1947
+ async function listSchemas(aiexDir) {
1948
+ try {
1949
+ const dir = path.join(aiexDir, "schema");
1950
+ return (await fs.readdir(dir)).filter((f) => f.endsWith(".json")).map((f) => f.replace(JSON_EXT_RE$1, "")).sort();
1951
+ } catch {
1952
+ return [];
1953
+ }
1954
+ }
1955
+
2169
1956
  //#endregion
2170
1957
  //#region src/infrastructure/extraction/insert-extracted-data.ts
2171
1958
  function convertValue(value, column) {
@@ -2511,7 +2298,7 @@ async function runAuditedExtraction(options) {
2511
2298
  filePath = input.filePath;
2512
2299
  inputProcessing = input.inputProcessing;
2513
2300
  inputQuality = input.quality;
2514
- if (!quiet) consola.info(`Input: ${formatInputProcessing$1(inputProcessing)}`);
2301
+ if (!quiet) consola.info(`Input: ${formatInputProcessing(inputProcessing)}`);
2515
2302
  await updateExtractionAuditRecord(aiexDir, audit.id, {
2516
2303
  inputProcessing,
2517
2304
  quality: inputQuality
@@ -2719,36 +2506,15 @@ async function runBatchExtraction(aiexDir, config, aiConfig, schemaName, dir, gl
2719
2506
  }
2720
2507
 
2721
2508
  //#endregion
2722
- //#region src/commands/extract.ts
2723
- function getIdArg(args) {
2724
- if (typeof args.id === "string") return args.id;
2725
- const positional = args._;
2726
- return Array.isArray(positional) && typeof positional[0] === "string" ? positional[0] : "";
2727
- }
2728
- function isExtractSubCommand(rawArgs) {
2729
- if (!Array.isArray(rawArgs)) return false;
2730
- return rawArgs.some((arg) => typeof arg === "string" && [
2731
- "history",
2732
- "show",
2733
- "retry",
2734
- "rm"
2735
- ].includes(arg));
2736
- }
2737
- function formatSource(source) {
2738
- return source.type === "file" ? source.fileName || "file" : "unknown";
2739
- }
2740
- function formatInputProcessing(input) {
2741
- if (!input) return "";
2742
- const handler = input.converter ? `${input.handler}(${input.converter})` : input.handler;
2743
- return ` [${input.mime ?? input.kind} -> ${handler}]`;
2744
- }
2745
- function formatQuality(quality, failureStage) {
2746
- if (failureStage) return ` [failed:${failureStage}]`;
2747
- if (quality?.input?.pdf) return ` [pdf:${quality.input.pdf.pageCount}p/${quality.input.pdf.textLength}chars${quality.input.pdf.fallbackUsed ? "/fallback" : ""}]`;
2748
- if (quality?.input?.ocr) return ` [ocr:${Math.round(quality.input.ocr.confidence * 100)}%/${quality.input.ocr.textLength}chars]`;
2749
- if (quality?.ai?.missingFieldRate !== void 0) return ` [missing:${Math.round(quality.ai.missingFieldRate * 100)}%]`;
2750
- return "";
2509
+ //#region src/commands/utils.ts
2510
+ function failCommand(message) {
2511
+ if (message) consola.error(message);
2512
+ outro(t("common.failed"));
2513
+ process.exitCode = 1;
2751
2514
  }
2515
+
2516
+ //#endregion
2517
+ //#region src/commands/extract.ts
2752
2518
  async function loadConfiguredAI(aiexDir) {
2753
2519
  const aiConfig = await readAIConfig(aiexDir);
2754
2520
  if (!aiConfig) {
@@ -2777,143 +2543,11 @@ function resolveModelOverride(aiConfig, modelName) {
2777
2543
  }
2778
2544
  return matched;
2779
2545
  }
2780
- const historyCommand = defineCommand({
2781
- meta: {
2782
- name: "history",
2783
- description: t("command.extract.history.description")
2784
- },
2785
- async run() {
2786
- const config = createMigrationConfig(process.cwd());
2787
- const records = await listExtractionAuditRecords(path.dirname(config.schemaPath));
2788
- if (records.length === 0) {
2789
- consola.info(t("command.extract.history.empty"));
2790
- return;
2791
- }
2792
- for (const record of records) {
2793
- const suffix = record.error ? ` — ${record.error}` : record.outputName ? ` — ${record.outputName}` : "";
2794
- consola.info(`${record.status.padEnd(9)} ${record.id} ${record.schemaName} ${formatSource(record.source)}${formatInputProcessing(record.inputProcessing)}${formatQuality(record.quality, record.failureStage)}${suffix}`);
2795
- }
2796
- }
2797
- });
2798
- const showCommand = defineCommand({
2799
- meta: {
2800
- name: "show",
2801
- description: t("command.extract.show.description")
2802
- },
2803
- args: { id: {
2804
- type: "string",
2805
- description: t("command.extract.show.args.id")
2806
- } },
2807
- async run({ args }) {
2808
- const id = getIdArg(args);
2809
- if (!id) {
2810
- failCommand(t("command.extract.history.errors.idRequired"));
2811
- return;
2812
- }
2813
- const config = createMigrationConfig(process.cwd());
2814
- const record = await readExtractionAuditRecord(path.dirname(config.schemaPath), id);
2815
- if (!record) {
2816
- failCommand(t("command.extract.history.errors.recordNotFound", { id }));
2817
- return;
2818
- }
2819
- consola.info(JSON.stringify(record, null, 2));
2820
- }
2821
- });
2822
- const retryCommand = defineCommand({
2823
- meta: {
2824
- name: "retry",
2825
- description: t("command.extract.retry.description")
2826
- },
2827
- args: {
2828
- id: {
2829
- type: "string",
2830
- description: t("command.extract.retry.args.id")
2831
- },
2832
- noInsert: {
2833
- type: "boolean",
2834
- description: t("command.extract.retry.args.noInsert"),
2835
- default: false
2836
- }
2837
- },
2838
- async run({ args }) {
2839
- intro(pc.inverse(" aiex extract retry "));
2840
- await initI18n();
2841
- const id = getIdArg(args);
2842
- if (!id) {
2843
- failCommand(t("command.extract.history.errors.idRequired"));
2844
- return;
2845
- }
2846
- const config = createMigrationConfig(process.cwd());
2847
- const aiexDir = path.dirname(config.schemaPath);
2848
- const record = await readExtractionAuditRecord(aiexDir, id);
2849
- if (!record) {
2850
- failCommand(t("command.extract.history.errors.recordNotFound", { id }));
2851
- return;
2852
- }
2853
- const aiConfig = await loadConfiguredAI(aiexDir);
2854
- if (!aiConfig) return;
2855
- const modelOverride = resolveModelOverride(aiConfig, record.modelName);
2856
- if (modelOverride === null) return;
2857
- try {
2858
- const result = await runAuditedExtraction({
2859
- aiexDir,
2860
- config,
2861
- aiConfig,
2862
- schemaName: record.schemaName,
2863
- source: record.source,
2864
- modelOverride,
2865
- retryOf: record.id,
2866
- insert: !args.noInsert,
2867
- force: true
2868
- });
2869
- if (!result.success) {
2870
- failCommand(result.error);
2871
- return;
2872
- }
2873
- outro(t("common.done"));
2874
- } catch (error) {
2875
- if (isMissingUploadFileError(error)) {
2876
- failCommand(MISSING_UPLOAD_FILE_TEXT);
2877
- return;
2878
- }
2879
- failCommand(error instanceof Error ? error.message : String(error));
2880
- }
2881
- }
2882
- });
2883
- const rmCommand = defineCommand({
2884
- meta: {
2885
- name: "rm",
2886
- description: t("command.extract.rm.description")
2887
- },
2888
- args: { id: {
2889
- type: "string",
2890
- description: t("command.extract.rm.args.id")
2891
- } },
2892
- async run({ args }) {
2893
- const id = getIdArg(args);
2894
- if (!id) {
2895
- failCommand(t("command.extract.history.errors.idRequired"));
2896
- return;
2897
- }
2898
- const config = createMigrationConfig(process.cwd());
2899
- if (!await deleteExtractionAuditRecord(path.dirname(config.schemaPath), id)) {
2900
- failCommand(t("command.extract.history.errors.recordNotFound", { id }));
2901
- return;
2902
- }
2903
- consola.success(t("command.extract.history.deleted", { id }));
2904
- }
2905
- });
2906
2546
  const extractCommand = defineCommand({
2907
2547
  meta: {
2908
2548
  name: "extract",
2909
2549
  description: t("command.extract.description")
2910
2550
  },
2911
- subCommands: {
2912
- history: historyCommand,
2913
- show: showCommand,
2914
- retry: retryCommand,
2915
- rm: rmCommand
2916
- },
2917
2551
  args: {
2918
2552
  schema: {
2919
2553
  type: "string",
@@ -2951,8 +2585,7 @@ const extractCommand = defineCommand({
2951
2585
  default: false
2952
2586
  }
2953
2587
  },
2954
- async run({ args, rawArgs }) {
2955
- if (isExtractSubCommand(rawArgs)) return;
2588
+ async run({ args }) {
2956
2589
  intro(pc.inverse(" aiex extract "));
2957
2590
  await initI18n();
2958
2591
  const config = createMigrationConfig(process.cwd());
@@ -3033,7 +2666,7 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
3033
2666
  }))
3034
2667
  });
3035
2668
  if (isCancel(schemaName)) {
3036
- cancel(t("common.cancelled"));
2669
+ cancel$1(t("common.cancelled"));
3037
2670
  return false;
3038
2671
  }
3039
2672
  const inputSource = await select({
@@ -3049,7 +2682,7 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
3049
2682
  }]
3050
2683
  });
3051
2684
  if (isCancel(inputSource)) {
3052
- cancel(t("common.cancelled"));
2685
+ cancel$1(t("common.cancelled"));
3053
2686
  return false;
3054
2687
  }
3055
2688
  if (inputSource === "file") {
@@ -3060,7 +2693,7 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
3060
2693
  }
3061
2694
  });
3062
2695
  if (isCancel(filePathStr)) {
3063
- cancel(t("common.cancelled"));
2696
+ cancel$1(t("common.cancelled"));
3064
2697
  return false;
3065
2698
  }
3066
2699
  const fp = filePathStr;
@@ -3069,7 +2702,7 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
3069
2702
  initialValue: false
3070
2703
  });
3071
2704
  if (isCancel(force)) {
3072
- cancel(t("common.cancelled"));
2705
+ cancel$1(t("common.cancelled"));
3073
2706
  return false;
3074
2707
  }
3075
2708
  return (await runAuditedExtraction({
@@ -3092,7 +2725,7 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
3092
2725
  }
3093
2726
  });
3094
2727
  if (isCancel(dirPath)) {
3095
- cancel(t("common.cancelled"));
2728
+ cancel$1(t("common.cancelled"));
3096
2729
  return false;
3097
2730
  }
3098
2731
  const force = await confirm({
@@ -3100,7 +2733,7 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
3100
2733
  initialValue: false
3101
2734
  });
3102
2735
  if (isCancel(force)) {
3103
- cancel(t("common.cancelled"));
2736
+ cancel$1(t("common.cancelled"));
3104
2737
  return false;
3105
2738
  }
3106
2739
  const result = await runBatchExtraction(aiexDir, config, aiConfig, schemaName, dirPath, void 0, modelOverride, { force });
@@ -3109,12 +2742,79 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
3109
2742
  }
3110
2743
  return false;
3111
2744
  }
3112
- function cancel(msg) {
2745
+ function cancel$1(msg) {
3113
2746
  consola.info(msg);
3114
2747
  outro(t("common.cancelled"));
3115
2748
  process.exitCode = 0;
3116
2749
  }
3117
2750
 
2751
+ //#endregion
2752
+ //#region src/domain/schema/migration-risk.ts
2753
+ function keyOf(entry) {
2754
+ return `${entry.table}.${entry.column}`;
2755
+ }
2756
+ function maxSeverity(items) {
2757
+ if (items.some((item) => item.severity === "high")) return "high";
2758
+ if (items.some((item) => item.severity === "medium")) return "medium";
2759
+ if (items.some((item) => item.severity === "low")) return "low";
2760
+ return "none";
2761
+ }
2762
+ function addRisk(items, severity, kind, table, column, message) {
2763
+ items.push({
2764
+ severity,
2765
+ kind,
2766
+ table,
2767
+ column,
2768
+ message
2769
+ });
2770
+ }
2771
+ function enumValues(entry) {
2772
+ return new Set((entry.constraints?.enumValues ?? []).map((value) => JSON.stringify(value)));
2773
+ }
2774
+ function isEnumNarrowed(previous, next) {
2775
+ const prev = enumValues(previous);
2776
+ const current = enumValues(next);
2777
+ if (prev.size === 0 || current.size === 0 || current.size >= prev.size) return false;
2778
+ return [...current].every((value) => prev.has(value));
2779
+ }
2780
+ function enumChanged(previous, next) {
2781
+ const prev = enumValues(previous);
2782
+ const current = enumValues(next);
2783
+ if (prev.size === 0 && current.size === 0) return false;
2784
+ if (prev.size !== current.size) return true;
2785
+ return [...prev].some((value) => !current.has(value));
2786
+ }
2787
+ function analyzeMigrationRisk(previousEntries, nextEntries) {
2788
+ const items = [];
2789
+ const previousByKey = new Map(previousEntries.map((entry) => [keyOf(entry), entry]));
2790
+ const nextByKey = new Map(nextEntries.map((entry) => [keyOf(entry), entry]));
2791
+ const previousTables = new Set(previousEntries.map((entry) => entry.table));
2792
+ const nextTables = new Set(nextEntries.map((entry) => entry.table));
2793
+ for (const table of previousTables) if (!nextTables.has(table)) addRisk(items, "high", "table_removed", table, void 0, `Table "${table}" will be removed.`);
2794
+ for (const table of nextTables) if (!previousTables.has(table)) addRisk(items, "low", "table_added", table, void 0, `Table "${table}" will be added.`);
2795
+ for (const [key, previous] of previousByKey) {
2796
+ const next = nextByKey.get(key);
2797
+ if (!next) {
2798
+ addRisk(items, "high", "column_removed", previous.table, previous.column, `Column "${key}" will be removed.`);
2799
+ continue;
2800
+ }
2801
+ if (previous.sqliteType !== next.sqliteType || previous.drizzleType !== next.drizzleType) addRisk(items, "high", "column_type_changed", previous.table, previous.column, `Column "${key}" type changes from ${previous.drizzleType} to ${next.drizzleType}.`);
2802
+ if (previous.nullable && !next.nullable) addRisk(items, "high", "nullable_tightened", previous.table, previous.column, `Column "${key}" changes from nullable to not null.`);
2803
+ else if (!previous.nullable && next.nullable) addRisk(items, "medium", "nullable_relaxed", previous.table, previous.column, `Column "${key}" changes from not null to nullable.`);
2804
+ if (!previous.unique && next.unique) addRisk(items, "high", "unique_added", previous.table, previous.column, `Column "${key}" adds a unique constraint.`);
2805
+ if (previous.primary !== next.primary) addRisk(items, "high", "primary_changed", previous.table, previous.column, `Column "${key}" primary key status changes.`);
2806
+ if (isEnumNarrowed(previous, next)) addRisk(items, "high", "enum_narrowed", previous.table, previous.column, `Column "${key}" enum values are narrowed.`);
2807
+ else if (enumChanged(previous, next)) addRisk(items, "medium", "enum_changed", previous.table, previous.column, `Column "${key}" enum values change.`);
2808
+ }
2809
+ for (const [key, next] of nextByKey) if (!previousByKey.has(key)) addRisk(items, next.nullable ? "low" : "medium", "column_added", next.table, next.column, `Column "${key}" will be added.`);
2810
+ const level = maxSeverity(items);
2811
+ return {
2812
+ level,
2813
+ items,
2814
+ hasHighRisk: level === "high"
2815
+ };
2816
+ }
2817
+
3118
2818
  //#endregion
3119
2819
  //#region src/infrastructure/runtime/package-paths.ts
3120
2820
  const __filename = fileURLToPath(import.meta.url);
@@ -3139,6 +2839,102 @@ function resolveHelperPath() {
3139
2839
  }
3140
2840
  }
3141
2841
 
2842
+ //#endregion
2843
+ //#region src/domain/schema/dialect.ts
2844
+ const TOP_LEVEL_KEYS = new Set([
2845
+ "$schema",
2846
+ "title",
2847
+ "description",
2848
+ "type",
2849
+ "table",
2850
+ "properties",
2851
+ "required",
2852
+ "examples"
2853
+ ]);
2854
+ const PROPERTY_KEYS = new Set([
2855
+ "description",
2856
+ "type",
2857
+ "format",
2858
+ "pattern",
2859
+ "enum",
2860
+ "primary",
2861
+ "autoIncrement",
2862
+ "unique",
2863
+ "default",
2864
+ "maxLength",
2865
+ "minLength",
2866
+ "minimum",
2867
+ "maximum",
2868
+ "examples",
2869
+ "xPrompt",
2870
+ "drizzle",
2871
+ "nested",
2872
+ "foreignKey",
2873
+ "properties",
2874
+ "items",
2875
+ "required"
2876
+ ]);
2877
+ const DRIZZLE_KEYS = new Set(["mode", "customType"]);
2878
+ const NESTED_KEYS = new Set(["enabled", "relation"]);
2879
+ const FOREIGN_KEY_KEYS = new Set(["table", "column"]);
2880
+ const TABLE_KEYS = new Set([
2881
+ "name",
2882
+ "timestamps",
2883
+ "softDelete"
2884
+ ]);
2885
+ const UNSUPPORTED_JSON_SCHEMA_KEYWORDS = new Set([
2886
+ "oneOf",
2887
+ "anyOf",
2888
+ "allOf",
2889
+ "not",
2890
+ "if",
2891
+ "then",
2892
+ "else",
2893
+ "const",
2894
+ "contains",
2895
+ "prefixItems",
2896
+ "additionalItems",
2897
+ "additionalProperties",
2898
+ "patternProperties",
2899
+ "propertyNames",
2900
+ "dependentRequired",
2901
+ "dependentSchemas",
2902
+ "dependencies",
2903
+ "unevaluatedItems",
2904
+ "unevaluatedProperties",
2905
+ "multipleOf",
2906
+ "exclusiveMinimum",
2907
+ "exclusiveMaximum"
2908
+ ]);
2909
+ function isRecord(value) {
2910
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2911
+ }
2912
+ function warnUnsupportedKey(warnings, path$1, key) {
2913
+ const reason = UNSUPPORTED_JSON_SCHEMA_KEYWORDS.has(key) ? "is not part of the AIEX Drizzle-backed schema dialect and cannot be mapped reliably" : "is not recognized by the AIEX Drizzle-backed schema dialect";
2914
+ warnings.push(`${path$1}.${key} ${reason}.`);
2915
+ }
2916
+ function inspectRecordKeys(warnings, value, path$1, allowedKeys) {
2917
+ if (!isRecord(value)) return;
2918
+ for (const key of Object.keys(value)) if (!allowedKeys.has(key)) warnUnsupportedKey(warnings, path$1, key);
2919
+ }
2920
+ function inspectProperty(warnings, property, path$1) {
2921
+ if (!isRecord(property)) return;
2922
+ inspectRecordKeys(warnings, property, path$1, PROPERTY_KEYS);
2923
+ inspectRecordKeys(warnings, property.drizzle, `${path$1}.drizzle`, DRIZZLE_KEYS);
2924
+ inspectRecordKeys(warnings, property.nested, `${path$1}.nested`, NESTED_KEYS);
2925
+ inspectRecordKeys(warnings, property.foreignKey, `${path$1}.foreignKey`, FOREIGN_KEY_KEYS);
2926
+ if (isRecord(property.properties)) for (const [name$1, child] of Object.entries(property.properties)) inspectProperty(warnings, child, `${path$1}.properties.${name$1}`);
2927
+ if (property.items) inspectProperty(warnings, property.items, `${path$1}.items`);
2928
+ }
2929
+ function collectDialectWarnings(schema, filePath) {
2930
+ if (!isRecord(schema)) return [];
2931
+ const warnings = [];
2932
+ inspectRecordKeys(warnings, schema, filePath, TOP_LEVEL_KEYS);
2933
+ inspectRecordKeys(warnings, schema.table, `${filePath}.table`, TABLE_KEYS);
2934
+ if (isRecord(schema.properties)) for (const [name$1, property] of Object.entries(schema.properties)) inspectProperty(warnings, property, `${filePath}.properties.${name$1}`);
2935
+ return warnings;
2936
+ }
2937
+
3142
2938
  //#endregion
3143
2939
  //#region src/application/schema/parse-all-schemas.ts
3144
2940
  function formatZodError(error, filePath) {
@@ -3149,6 +2945,7 @@ function parseAllSchemas(entries) {
3149
2945
  const relations = [];
3150
2946
  const reverseRelations = [];
3151
2947
  const warnings = [];
2948
+ const mapping = [];
3152
2949
  for (const { filePath, content } of entries) {
3153
2950
  let schema;
3154
2951
  try {
@@ -3160,11 +2957,16 @@ function parseAllSchemas(entries) {
3160
2957
  };
3161
2958
  }
3162
2959
  try {
2960
+ warnings.push(...collectDialectWarnings(schema, filePath));
3163
2961
  const result = parseJsonSchema(JsonSchemaDefinitionSchema.parse(schema));
3164
2962
  tables.push(...result.tables);
3165
2963
  relations.push(...result.relations);
3166
2964
  reverseRelations.push(...result.reverseRelations);
3167
2965
  warnings.push(...result.warnings);
2966
+ mapping.push(...(result.mapping ?? []).map((entry) => ({
2967
+ ...entry,
2968
+ schemaPath: `${filePath}${entry.schemaPath.slice(1)}`
2969
+ })));
3168
2970
  } catch (e) {
3169
2971
  if (e instanceof ZodError) return {
3170
2972
  success: false,
@@ -3180,6 +2982,7 @@ function parseAllSchemas(entries) {
3180
2982
  relations,
3181
2983
  reverseRelations,
3182
2984
  warnings,
2985
+ mapping,
3183
2986
  drizzleCode: generateDrizzleSchema({
3184
2987
  tables,
3185
2988
  relations,
@@ -3193,6 +2996,24 @@ function parseAllSchemas(entries) {
3193
2996
  //#endregion
3194
2997
  //#region src/application/schema/schema-sync.ts
3195
2998
  const execFileAsync = promisify(execFile);
2999
+ const NO_RISK_REPORT = {
3000
+ level: "none",
3001
+ items: [],
3002
+ hasHighRisk: false
3003
+ };
3004
+ function schemaMapPath(config) {
3005
+ return path.join(path.dirname(config.drizzleSchemaPath), "schema-map.json");
3006
+ }
3007
+ async function readPreviousSchemaMap(config) {
3008
+ try {
3009
+ const content = await fs.readFile(schemaMapPath(config), "utf-8");
3010
+ const parsed = JSON.parse(content);
3011
+ if (Array.isArray(parsed.baselineEntries)) return parsed.baselineEntries;
3012
+ return Array.isArray(parsed.entries) ? parsed.entries : [];
3013
+ } catch {
3014
+ return [];
3015
+ }
3016
+ }
3196
3017
  async function listSchemaFiles(schemaDir) {
3197
3018
  try {
3198
3019
  return (await fs.readdir(schemaDir)).filter((f) => f.endsWith(".json")).map((f) => path.join(schemaDir, f)).sort();
@@ -3200,7 +3021,8 @@ async function listSchemaFiles(schemaDir) {
3200
3021
  return [];
3201
3022
  }
3202
3023
  }
3203
- async function generateSchemaFromFiles(schemaFiles, config) {
3024
+ async function generateSchemaFromFiles(schemaFiles, config, options = {}) {
3025
+ const previousMapping = await readPreviousSchemaMap(config);
3204
3026
  const result = parseAllSchemas(await Promise.all(schemaFiles.map(async (filePath) => {
3205
3027
  return {
3206
3028
  filePath,
@@ -3213,17 +3035,30 @@ async function generateSchemaFromFiles(schemaFiles, config) {
3213
3035
  warnings: [],
3214
3036
  schemaCount: schemaFiles.length,
3215
3037
  tables: 0,
3216
- relations: 0
3038
+ relations: 0,
3039
+ mappingEntries: 0,
3040
+ riskReport: NO_RISK_REPORT
3217
3041
  };
3218
- const { tables, relations, reverseRelations, warnings, drizzleCode } = result.data;
3042
+ const { tables, relations, reverseRelations, warnings, mapping, drizzleCode } = result.data;
3043
+ const riskReport = previousMapping.length > 0 ? analyzeMigrationRisk(previousMapping, mapping) : NO_RISK_REPORT;
3219
3044
  await fs.mkdir(path.dirname(config.drizzleSchemaPath), { recursive: true });
3220
3045
  await fs.writeFile(config.drizzleSchemaPath, drizzleCode);
3046
+ await fs.writeFile(schemaMapPath(config), `${JSON.stringify({
3047
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3048
+ dialect: "aiex-drizzle-sqlite",
3049
+ entries: mapping,
3050
+ baselineEntries: riskReport.hasHighRisk && !options.force ? previousMapping : void 0,
3051
+ warnings,
3052
+ migrationRisk: riskReport
3053
+ }, null, 2)}\n`);
3221
3054
  return {
3222
3055
  success: true,
3223
3056
  warnings,
3224
3057
  schemaCount: schemaFiles.length,
3225
3058
  tables: tables.length,
3226
- relations: relations.length + reverseRelations.length
3059
+ relations: relations.length + reverseRelations.length,
3060
+ mappingEntries: mapping.length,
3061
+ riskReport
3227
3062
  };
3228
3063
  }
3229
3064
  function parseMigrationOutput(stdout, stderr) {
@@ -3275,24 +3110,34 @@ async function runSchemaSync(config, options = {}) {
3275
3110
  warnings: [],
3276
3111
  schemaCount: 0,
3277
3112
  tables: 0,
3278
- relations: 0
3113
+ relations: 0,
3114
+ mappingEntries: 0,
3115
+ riskReport: NO_RISK_REPORT
3279
3116
  };
3280
- const generated = await generateSchemaFromFiles(schemaFiles, config);
3117
+ const generated = await generateSchemaFromFiles(schemaFiles, config, { force: options.force });
3281
3118
  if (!generated.success) return {
3282
3119
  success: false,
3283
3120
  error: generated.error,
3284
3121
  warnings: generated.warnings,
3285
3122
  schemaCount: generated.schemaCount,
3286
3123
  tables: generated.tables,
3287
- relations: generated.relations
3288
- };
3289
- if (options.generateOnly) return {
3290
- success: true,
3291
- warnings: generated.warnings,
3292
- schemaCount: generated.schemaCount,
3293
- tables: generated.tables,
3294
- relations: generated.relations
3124
+ relations: generated.relations,
3125
+ mappingEntries: generated.mappingEntries,
3126
+ riskReport: generated.riskReport
3295
3127
  };
3128
+ if (options.generateOnly || generated.riskReport.hasHighRisk && !options.force) {
3129
+ const blockedByRisk = generated.riskReport.hasHighRisk && !options.force && !options.generateOnly;
3130
+ return {
3131
+ success: !blockedByRisk,
3132
+ error: blockedByRisk ? t("errors.schema.highRiskMigrationBlocked") : void 0,
3133
+ warnings: generated.warnings,
3134
+ schemaCount: generated.schemaCount,
3135
+ tables: generated.tables,
3136
+ relations: generated.relations,
3137
+ mappingEntries: generated.mappingEntries,
3138
+ riskReport: generated.riskReport
3139
+ };
3140
+ }
3296
3141
  const migration = await runSchemaMigration(config, options.migrationName);
3297
3142
  return {
3298
3143
  success: migration.success,
@@ -3301,6 +3146,8 @@ async function runSchemaSync(config, options = {}) {
3301
3146
  schemaCount: generated.schemaCount,
3302
3147
  tables: generated.tables,
3303
3148
  relations: generated.relations,
3149
+ mappingEntries: generated.mappingEntries,
3150
+ riskReport: generated.riskReport,
3304
3151
  migration
3305
3152
  };
3306
3153
  }
@@ -3322,6 +3169,11 @@ const schemaCommand = defineCommand({
3322
3169
  name: {
3323
3170
  type: "string",
3324
3171
  description: t("command.schema.args.name")
3172
+ },
3173
+ force: {
3174
+ type: "boolean",
3175
+ description: t("command.schema.args.force"),
3176
+ default: false
3325
3177
  }
3326
3178
  },
3327
3179
  async run({ args }) {
@@ -3336,7 +3188,7 @@ const schemaCommand = defineCommand({
3336
3188
  }
3337
3189
  const s1 = spinner();
3338
3190
  s1.start(t("command.schema.generating"));
3339
- const generated = await generateSchemaFromFiles(schemaFiles, config);
3191
+ const generated = await generateSchemaFromFiles(schemaFiles, config, { force: args.force });
3340
3192
  for (const warning of generated.warnings) consola.warn(warning);
3341
3193
  if (generated.success) consola.success(t("command.schema.generated", {
3342
3194
  path: pc.cyan(".aiex/drizzle/schema.ts"),
@@ -3348,10 +3200,21 @@ const schemaCommand = defineCommand({
3348
3200
  failCommand(t("common.failed"));
3349
3201
  return;
3350
3202
  }
3203
+ if (generated.riskReport.items.length > 0) {
3204
+ consola.info(t("command.schema.riskSummary", {
3205
+ level: generated.riskReport.level,
3206
+ count: generated.riskReport.items.length
3207
+ }));
3208
+ for (const item of generated.riskReport.items) consola.warn(`[${item.severity}] ${item.message}`);
3209
+ }
3351
3210
  if (args.generate) {
3352
3211
  outro(t("command.schema.runWithoutGenerate"));
3353
3212
  return;
3354
3213
  }
3214
+ if (generated.riskReport.hasHighRisk && !args.force) {
3215
+ failCommand(t("command.schema.highRiskBlocked", { flag: pc.cyan("--force") }));
3216
+ return;
3217
+ }
3355
3218
  const s2 = spinner();
3356
3219
  s2.start(t("command.schema.runningMigrations"));
3357
3220
  const migration = await runSchemaMigration(config, args.name);
@@ -3549,6 +3412,14 @@ const watchCommand = defineCommand({
3549
3412
  async run({ args }) {
3550
3413
  intro(pc.inverse(" aiex watch "));
3551
3414
  await initI18n();
3415
+ const config = createMigrationConfig(process.cwd());
3416
+ const aiexDir = path.dirname(config.schemaPath);
3417
+ if (!args.schema && !args.dir && !args.model) {
3418
+ const aiConfig$1 = await loadConfiguredAI(aiexDir);
3419
+ if (!aiConfig$1) return;
3420
+ if (await runInteractiveWatch(aiexDir, config, aiConfig$1)) consola.info(t("command.watch.events.pressCtrlC"));
3421
+ return;
3422
+ }
3552
3423
  if (!args.schema) {
3553
3424
  failCommand(t("command.watch.errors.schemaRequired"));
3554
3425
  return;
@@ -3557,33 +3428,18 @@ const watchCommand = defineCommand({
3557
3428
  failCommand(t("command.watch.errors.dirRequired"));
3558
3429
  return;
3559
3430
  }
3560
- const config = createMigrationConfig(process.cwd());
3561
- const aiexDir = path.dirname(config.schemaPath);
3562
3431
  const schemaLoad = await loadSchema(config, args.schema);
3563
3432
  if (!schemaLoad.schema) {
3564
3433
  failCommand(schemaLoad.error || t("command.watch.errors.schemaNotFound", { name: args.schema }));
3565
3434
  return;
3566
3435
  }
3567
- let watchDirStat;
3568
- try {
3569
- watchDirStat = fs$1.statSync(args.dir);
3570
- } catch (e) {
3571
- failCommand(t("command.watch.errors.dirNotExist", {
3572
- dir: args.dir,
3573
- error: e instanceof Error ? e.message : String(e)
3574
- }));
3575
- return;
3576
- }
3577
- if (!watchDirStat.isDirectory()) {
3578
- failCommand(t("command.watch.errors.notADirectory", { dir: args.dir }));
3579
- return;
3580
- }
3581
3436
  const watchDirAbs = path.resolve(args.dir);
3437
+ if (!validateWatchDir(watchDirAbs)) return;
3582
3438
  const aiConfig = await loadConfiguredAI(aiexDir);
3583
3439
  if (!aiConfig) return;
3584
3440
  const modelOverride = resolveModelOverride(aiConfig, args.model);
3585
3441
  if (modelOverride === null) return;
3586
- const watcher = startWatcher({
3442
+ registerCleanup(startWatch({
3587
3443
  aiexDir,
3588
3444
  config,
3589
3445
  aiConfig,
@@ -3591,18 +3447,112 @@ const watchCommand = defineCommand({
3591
3447
  watchDir: watchDirAbs,
3592
3448
  modelOverride,
3593
3449
  insert: !args.noInsert
3594
- });
3595
- const cleanup = async () => {
3596
- consola.info(t("command.watch.events.stopped"));
3597
- await watcher.close();
3598
- consola.success(t("command.watch.events.stoppedOk"));
3599
- process.exit(0);
3600
- };
3601
- process.on("SIGINT", cleanup);
3602
- process.on("SIGTERM", cleanup);
3450
+ }));
3603
3451
  consola.info(t("command.watch.events.pressCtrlC"));
3604
3452
  }
3605
3453
  });
3454
+ async function runInteractiveWatch(aiexDir, config, aiConfig) {
3455
+ const schemas = await listSchemas(aiexDir);
3456
+ if (schemas.length === 0) {
3457
+ failCommand(t("command.extract.errors.noSchemas", {
3458
+ path: pc.cyan(".aiex/schema/"),
3459
+ cmd: pc.cyan("aiex web")
3460
+ }));
3461
+ return false;
3462
+ }
3463
+ const schemaName = await select({
3464
+ message: t("command.watch.interactive.selectSchema"),
3465
+ options: schemas.map((s) => ({
3466
+ label: s,
3467
+ value: s
3468
+ }))
3469
+ });
3470
+ if (isCancel(schemaName)) {
3471
+ cancel(t("common.cancelled"));
3472
+ return false;
3473
+ }
3474
+ const dirPath = await text({
3475
+ message: t("command.watch.interactive.enterDirPath"),
3476
+ validate(value) {
3477
+ if (!value || value.trim().length === 0) return t("command.watch.interactive.dirPathRequired");
3478
+ }
3479
+ });
3480
+ if (isCancel(dirPath)) {
3481
+ cancel(t("common.cancelled"));
3482
+ return false;
3483
+ }
3484
+ const selectedModel = await select({
3485
+ message: t("command.watch.interactive.selectModel"),
3486
+ options: [{
3487
+ label: t("command.watch.interactive.autoModel"),
3488
+ value: ""
3489
+ }, ...aiConfig.provider.models.map((model) => ({
3490
+ label: model.name,
3491
+ value: model.name
3492
+ }))]
3493
+ });
3494
+ if (isCancel(selectedModel)) {
3495
+ cancel(t("common.cancelled"));
3496
+ return false;
3497
+ }
3498
+ const noInsert = await confirm({
3499
+ message: t("command.watch.interactive.askNoInsert"),
3500
+ initialValue: false
3501
+ });
3502
+ if (isCancel(noInsert)) {
3503
+ cancel(t("common.cancelled"));
3504
+ return false;
3505
+ }
3506
+ const watchDir = path.resolve(dirPath);
3507
+ if (!validateWatchDir(watchDir)) return false;
3508
+ const modelOverride = resolveModelOverride(aiConfig, selectedModel ? selectedModel : void 0);
3509
+ if (modelOverride === null) return false;
3510
+ registerCleanup(startWatch({
3511
+ aiexDir,
3512
+ config,
3513
+ aiConfig,
3514
+ schemaName,
3515
+ watchDir,
3516
+ modelOverride,
3517
+ insert: !noInsert
3518
+ }));
3519
+ return true;
3520
+ }
3521
+ function validateWatchDir(dir) {
3522
+ let watchDirStat;
3523
+ try {
3524
+ watchDirStat = fs$1.statSync(dir);
3525
+ } catch (e) {
3526
+ failCommand(t("command.watch.errors.dirNotExist", {
3527
+ dir,
3528
+ error: e instanceof Error ? e.message : String(e)
3529
+ }));
3530
+ return false;
3531
+ }
3532
+ if (!watchDirStat.isDirectory()) {
3533
+ failCommand(t("command.watch.errors.notADirectory", { dir }));
3534
+ return false;
3535
+ }
3536
+ return true;
3537
+ }
3538
+ function startWatch(options) {
3539
+ return startWatcher(options);
3540
+ }
3541
+ function registerCleanup(watcher) {
3542
+ const cleanup = async () => {
3543
+ consola.info(t("command.watch.events.stopped"));
3544
+ await watcher.close();
3545
+ consola.success(t("command.watch.events.stoppedOk"));
3546
+ process.exit(0);
3547
+ };
3548
+ process.on("SIGINT", cleanup);
3549
+ process.on("SIGTERM", cleanup);
3550
+ }
3551
+ function cancel(msg) {
3552
+ consola.info(msg);
3553
+ outro(t("common.cancelled"));
3554
+ process.exitCode = 0;
3555
+ }
3606
3556
 
3607
3557
  //#endregion
3608
3558
  //#region src/domain/ai-extraction/model-capabilities.json
@@ -16928,12 +16878,13 @@ function schemaRoutes(config) {
16928
16878
  app.post("/migrate", async (c) => {
16929
16879
  try {
16930
16880
  await ensureDir();
16931
- const result = await runSchemaSync(config);
16881
+ const result = await runSchemaSync(config, { force: c.req.query("force") === "true" });
16932
16882
  if (!result.success) {
16933
- const status = result.schemaCount === 0 ? 400 : 500;
16883
+ const status = result.schemaCount === 0 ? 400 : result.riskReport.hasHighRisk ? 409 : 500;
16934
16884
  return c.json({
16935
16885
  success: false,
16936
- error: result.error || t("server.migrationFailed")
16886
+ error: result.error || t("server.migrationFailed"),
16887
+ riskReport: result.riskReport
16937
16888
  }, status);
16938
16889
  }
16939
16890
  return c.json({
@@ -16942,7 +16893,8 @@ function schemaRoutes(config) {
16942
16893
  tag: result.migration?.tag,
16943
16894
  tables: result.tables,
16944
16895
  relations: result.relations,
16945
- warnings: result.warnings
16896
+ warnings: result.warnings,
16897
+ riskReport: result.riskReport
16946
16898
  });
16947
16899
  } catch (error) {
16948
16900
  return c.json({
@@ -17062,7 +17014,6 @@ const subCommands = {
17062
17014
  schema: schemaCommand,
17063
17015
  extract: extractCommand,
17064
17016
  watch: watchCommand,
17065
- dump: dumpCommand,
17066
17017
  completion: completionCommand,
17067
17018
  doctor: doctorCommand
17068
17019
  };