aiex-cli 0.1.1-beta.1 → 0.1.1-beta.3

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/README.md CHANGED
@@ -32,7 +32,7 @@ aiex watch -s invoice -d ./watch_folder # watch folder daemon for automatic extr
32
32
 
33
33
  ## ✨ Features
34
34
 
35
- - **JSON Schema → SQLite** — Define tables as JSON Schema files, generate Drizzle ORM schema, and migrate to SQLite
35
+ - **AIEX JSON Schema → SQLite** — Define tables with a Drizzle-backed JSON Schema dialect, generate Drizzle ORM schema, and migrate to SQLite
36
36
  - **Web Configuration & Viewer** — Browser-based UI for designing schemas, configuring integrations, previewing prompts, and browsing extracted data
37
37
  - **AI Extraction** — Extract structured data from files (text, images, PDFs) using any OpenAI-compatible provider (OpenAI, Anthropic, Ollama, DeepSeek, local models, etc.)
38
38
  - **Interactive Mode** — Run `aiex extract` without arguments for a guided extraction workflow
@@ -61,7 +61,7 @@ Opens a browser UI where you can visually design and manage your schemas, config
61
61
  aiex schema
62
62
  ```
63
63
 
64
- Converts your JSON Schema files into a SQLite database with full migration support.
64
+ Converts AIEX JSON Schema files into a SQLite database with full migration support. AIEX uses a Drizzle-backed schema dialect rather than the full JSON Schema specification; see [Docs/schema-dialect.md](Docs/schema-dialect.md) for the supported mapping surface.
65
65
 
66
66
  ### 3. Extract Data
67
67
 
@@ -107,6 +107,7 @@ Runs a background watcher daemon to monitor a folder for new incoming files (suc
107
107
  | --- | --- |
108
108
  | `aiex schema` | Parse JSON Schema files and migrate to SQLite |
109
109
  | `aiex schema --generate` | Generate Drizzle schema code only (skip migration) |
110
+ | `aiex schema --force` | Allow a high-risk schema migration after reviewing the migration risk report |
110
111
  | `aiex web` | Launch visual schema/configuration UI and data viewer in browser |
111
112
  | `aiex extract` | Interactive mode — prompts for schema and file/directory input |
112
113
  | `aiex extract -s <name> -f <file>` | Extract structured data from a file and insert into SQLite database |
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-CaSMqQWx.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-DbJcRtaQ.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import fs from "node:fs/promises";
4
4
  import os from "node:os";
@@ -1162,16 +1162,16 @@ function getFileHash(filePath) {
1162
1162
  //#endregion
1163
1163
  //#region src/domain/ai-extraction/evidence.ts
1164
1164
  const NUMERIC_RE = /^-?\d+(?:\.\d+)?$/;
1165
- function isRecord$2(value) {
1165
+ function isRecord$3(value) {
1166
1166
  return typeof value === "object" && value !== null && !Array.isArray(value);
1167
1167
  }
1168
1168
  function stripEvidence(data) {
1169
- if (!isRecord$2(data)) return { data };
1169
+ if (!isRecord$3(data)) return { data };
1170
1170
  const { _evidence, ...businessData } = data;
1171
- if (!isRecord$2(_evidence)) return { data: businessData };
1171
+ if (!isRecord$3(_evidence)) return { data: businessData };
1172
1172
  return {
1173
1173
  data: businessData,
1174
- 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]))
1175
1175
  };
1176
1176
  }
1177
1177
  function findExactUnique(text$1, quote) {
@@ -1203,7 +1203,7 @@ function quoteContainsValue(quote, value, property) {
1203
1203
  return false;
1204
1204
  }
1205
1205
  function verifyFieldEvidence(input) {
1206
- 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;
1207
1207
  const verified = {};
1208
1208
  for (const [field, raw] of Object.entries(input.rawEvidence)) {
1209
1209
  const property = input.schema.properties[field];
@@ -1437,7 +1437,7 @@ function propertyToExtractionSchema(property) {
1437
1437
  }
1438
1438
  return { type: nullableType(property.type) };
1439
1439
  }
1440
- function isRecord$1(value) {
1440
+ function isRecord$2(value) {
1441
1441
  return typeof value === "object" && value !== null && !Array.isArray(value);
1442
1442
  }
1443
1443
  function schemaToExtractionOutputSchema(schema) {
@@ -1475,7 +1475,7 @@ function validatePropertyValue(path$1, property, value, issues) {
1475
1475
  }
1476
1476
  return;
1477
1477
  case "object":
1478
- if (!isRecord$1(value)) {
1478
+ if (!isRecord$2(value)) {
1479
1479
  issues.push(`${path$1}: expected object or null`);
1480
1480
  return;
1481
1481
  }
@@ -1498,7 +1498,7 @@ function validateProperties(basePath, properties, data, issues) {
1498
1498
  }
1499
1499
  }
1500
1500
  function validateExtractedData(schema, data) {
1501
- if (!isRecord$1(data)) return {
1501
+ if (!isRecord$2(data)) return {
1502
1502
  success: false,
1503
1503
  error: "Extracted data must be a JSON object."
1504
1504
  };
@@ -1519,11 +1519,11 @@ const EVIDENCE_INSTRUCTIONS = `Evidence requirements:
1519
1519
  - The quote must be an exact contiguous substring copied from the input text.
1520
1520
  - Do not invent offsets. Only provide quotes.
1521
1521
  - If no exact quote supports a field, omit that field from "_evidence".`;
1522
- function isRecord(value) {
1522
+ function isRecord$1(value) {
1523
1523
  return typeof value === "object" && value !== null && !Array.isArray(value);
1524
1524
  }
1525
1525
  function withEvidenceSchema(schema) {
1526
- const properties = isRecord(schema.properties) ? schema.properties : {};
1526
+ const properties = isRecord$1(schema.properties) ? schema.properties : {};
1527
1527
  const required = Array.isArray(schema.required) ? schema.required : [];
1528
1528
  return {
1529
1529
  ...schema,
@@ -2748,6 +2748,73 @@ function cancel$1(msg) {
2748
2748
  process.exitCode = 0;
2749
2749
  }
2750
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
+
2751
2818
  //#endregion
2752
2819
  //#region src/infrastructure/runtime/package-paths.ts
2753
2820
  const __filename = fileURLToPath(import.meta.url);
@@ -2772,6 +2839,102 @@ function resolveHelperPath() {
2772
2839
  }
2773
2840
  }
2774
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"]);
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
+
2775
2938
  //#endregion
2776
2939
  //#region src/application/schema/parse-all-schemas.ts
2777
2940
  function formatZodError(error, filePath) {
@@ -2782,6 +2945,7 @@ function parseAllSchemas(entries) {
2782
2945
  const relations = [];
2783
2946
  const reverseRelations = [];
2784
2947
  const warnings = [];
2948
+ const mapping = [];
2785
2949
  for (const { filePath, content } of entries) {
2786
2950
  let schema;
2787
2951
  try {
@@ -2793,11 +2957,16 @@ function parseAllSchemas(entries) {
2793
2957
  };
2794
2958
  }
2795
2959
  try {
2960
+ warnings.push(...collectDialectWarnings(schema, filePath));
2796
2961
  const result = parseJsonSchema(JsonSchemaDefinitionSchema.parse(schema));
2797
2962
  tables.push(...result.tables);
2798
2963
  relations.push(...result.relations);
2799
2964
  reverseRelations.push(...result.reverseRelations);
2800
2965
  warnings.push(...result.warnings);
2966
+ mapping.push(...(result.mapping ?? []).map((entry) => ({
2967
+ ...entry,
2968
+ schemaPath: `${filePath}${entry.schemaPath.slice(1)}`
2969
+ })));
2801
2970
  } catch (e) {
2802
2971
  if (e instanceof ZodError) return {
2803
2972
  success: false,
@@ -2813,6 +2982,7 @@ function parseAllSchemas(entries) {
2813
2982
  relations,
2814
2983
  reverseRelations,
2815
2984
  warnings,
2985
+ mapping,
2816
2986
  drizzleCode: generateDrizzleSchema({
2817
2987
  tables,
2818
2988
  relations,
@@ -2826,6 +2996,24 @@ function parseAllSchemas(entries) {
2826
2996
  //#endregion
2827
2997
  //#region src/application/schema/schema-sync.ts
2828
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
+ }
2829
3017
  async function listSchemaFiles(schemaDir) {
2830
3018
  try {
2831
3019
  return (await fs.readdir(schemaDir)).filter((f) => f.endsWith(".json")).map((f) => path.join(schemaDir, f)).sort();
@@ -2833,7 +3021,8 @@ async function listSchemaFiles(schemaDir) {
2833
3021
  return [];
2834
3022
  }
2835
3023
  }
2836
- async function generateSchemaFromFiles(schemaFiles, config) {
3024
+ async function generateSchemaFromFiles(schemaFiles, config, options = {}) {
3025
+ const previousMapping = await readPreviousSchemaMap(config);
2837
3026
  const result = parseAllSchemas(await Promise.all(schemaFiles.map(async (filePath) => {
2838
3027
  return {
2839
3028
  filePath,
@@ -2846,17 +3035,30 @@ async function generateSchemaFromFiles(schemaFiles, config) {
2846
3035
  warnings: [],
2847
3036
  schemaCount: schemaFiles.length,
2848
3037
  tables: 0,
2849
- relations: 0
3038
+ relations: 0,
3039
+ mappingEntries: 0,
3040
+ riskReport: NO_RISK_REPORT
2850
3041
  };
2851
- 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;
2852
3044
  await fs.mkdir(path.dirname(config.drizzleSchemaPath), { recursive: true });
2853
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`);
2854
3054
  return {
2855
3055
  success: true,
2856
3056
  warnings,
2857
3057
  schemaCount: schemaFiles.length,
2858
3058
  tables: tables.length,
2859
- relations: relations.length + reverseRelations.length
3059
+ relations: relations.length + reverseRelations.length,
3060
+ mappingEntries: mapping.length,
3061
+ riskReport
2860
3062
  };
2861
3063
  }
2862
3064
  function parseMigrationOutput(stdout, stderr) {
@@ -2908,24 +3110,34 @@ async function runSchemaSync(config, options = {}) {
2908
3110
  warnings: [],
2909
3111
  schemaCount: 0,
2910
3112
  tables: 0,
2911
- relations: 0
3113
+ relations: 0,
3114
+ mappingEntries: 0,
3115
+ riskReport: NO_RISK_REPORT
2912
3116
  };
2913
- const generated = await generateSchemaFromFiles(schemaFiles, config);
3117
+ const generated = await generateSchemaFromFiles(schemaFiles, config, { force: options.force });
2914
3118
  if (!generated.success) return {
2915
3119
  success: false,
2916
3120
  error: generated.error,
2917
3121
  warnings: generated.warnings,
2918
3122
  schemaCount: generated.schemaCount,
2919
3123
  tables: generated.tables,
2920
- relations: generated.relations
2921
- };
2922
- if (options.generateOnly) return {
2923
- success: true,
2924
- warnings: generated.warnings,
2925
- schemaCount: generated.schemaCount,
2926
- tables: generated.tables,
2927
- relations: generated.relations
3124
+ relations: generated.relations,
3125
+ mappingEntries: generated.mappingEntries,
3126
+ riskReport: generated.riskReport
2928
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
+ }
2929
3141
  const migration = await runSchemaMigration(config, options.migrationName);
2930
3142
  return {
2931
3143
  success: migration.success,
@@ -2934,6 +3146,8 @@ async function runSchemaSync(config, options = {}) {
2934
3146
  schemaCount: generated.schemaCount,
2935
3147
  tables: generated.tables,
2936
3148
  relations: generated.relations,
3149
+ mappingEntries: generated.mappingEntries,
3150
+ riskReport: generated.riskReport,
2937
3151
  migration
2938
3152
  };
2939
3153
  }
@@ -2955,6 +3169,11 @@ const schemaCommand = defineCommand({
2955
3169
  name: {
2956
3170
  type: "string",
2957
3171
  description: t("command.schema.args.name")
3172
+ },
3173
+ force: {
3174
+ type: "boolean",
3175
+ description: t("command.schema.args.force"),
3176
+ default: false
2958
3177
  }
2959
3178
  },
2960
3179
  async run({ args }) {
@@ -2969,7 +3188,7 @@ const schemaCommand = defineCommand({
2969
3188
  }
2970
3189
  const s1 = spinner();
2971
3190
  s1.start(t("command.schema.generating"));
2972
- const generated = await generateSchemaFromFiles(schemaFiles, config);
3191
+ const generated = await generateSchemaFromFiles(schemaFiles, config, { force: args.force });
2973
3192
  for (const warning of generated.warnings) consola.warn(warning);
2974
3193
  if (generated.success) consola.success(t("command.schema.generated", {
2975
3194
  path: pc.cyan(".aiex/drizzle/schema.ts"),
@@ -2981,10 +3200,21 @@ const schemaCommand = defineCommand({
2981
3200
  failCommand(t("common.failed"));
2982
3201
  return;
2983
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
+ }
2984
3210
  if (args.generate) {
2985
3211
  outro(t("command.schema.runWithoutGenerate"));
2986
3212
  return;
2987
3213
  }
3214
+ if (generated.riskReport.hasHighRisk && !args.force) {
3215
+ failCommand(t("command.schema.highRiskBlocked", { flag: pc.cyan("--force") }));
3216
+ return;
3217
+ }
2988
3218
  const s2 = spinner();
2989
3219
  s2.start(t("command.schema.runningMigrations"));
2990
3220
  const migration = await runSchemaMigration(config, args.name);
@@ -16648,12 +16878,13 @@ function schemaRoutes(config) {
16648
16878
  app.post("/migrate", async (c) => {
16649
16879
  try {
16650
16880
  await ensureDir();
16651
- const result = await runSchemaSync(config);
16881
+ const result = await runSchemaSync(config, { force: c.req.query("force") === "true" });
16652
16882
  if (!result.success) {
16653
- const status = result.schemaCount === 0 ? 400 : 500;
16883
+ const status = result.schemaCount === 0 ? 400 : result.riskReport.hasHighRisk ? 409 : 500;
16654
16884
  return c.json({
16655
16885
  success: false,
16656
- error: result.error || t("server.migrationFailed")
16886
+ error: result.error || t("server.migrationFailed"),
16887
+ riskReport: result.riskReport
16657
16888
  }, status);
16658
16889
  }
16659
16890
  return c.json({
@@ -16662,7 +16893,8 @@ function schemaRoutes(config) {
16662
16893
  tag: result.migration?.tag,
16663
16894
  tables: result.tables,
16664
16895
  relations: result.relations,
16665
- warnings: result.warnings
16896
+ warnings: result.warnings,
16897
+ riskReport: result.riskReport
16666
16898
  });
16667
16899
  } catch (error) {
16668
16900
  return c.json({