substrate-ai 0.7.0 → 0.8.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.
@@ -158,6 +158,17 @@ var DoltDatabaseAdapter = class {
158
158
  //#region src/persistence/memory-adapter.ts
159
159
  var InMemoryDatabaseAdapter = class {
160
160
  _tables = new Map();
161
+ _indexes = [];
162
+ /** Maps table name → auto-increment column name */
163
+ _autoIncrementCols = new Map();
164
+ /** Maps table name → last assigned auto-increment value */
165
+ _autoIncrementCounters = new Map();
166
+ /** Maps "tableName.colName" → default value expression ('CURRENT_TIMESTAMP' → ISO string, else literal) */
167
+ _columnDefaults = new Map();
168
+ /** Maps table name → array of primary key column names */
169
+ _primaryKeys = new Map();
170
+ /** Maps table name → ordered list of column names (from CREATE TABLE) */
171
+ _tableColumns = new Map();
161
172
  async query(sql, params) {
162
173
  const rows = this._execute(sql.trim(), params);
163
174
  return rows;
@@ -165,14 +176,23 @@ var InMemoryDatabaseAdapter = class {
165
176
  async exec(sql) {
166
177
  this._execute(sql.trim(), void 0);
167
178
  }
179
+ querySync(sql, params) {
180
+ const rows = this._execute(sql.trim(), params);
181
+ return rows;
182
+ }
183
+ execSync(sql) {
184
+ this._execute(sql.trim(), void 0);
185
+ }
168
186
  async transaction(fn) {
169
187
  const snapshot = new Map();
170
188
  for (const [name, rows] of this._tables) snapshot.set(name, rows.map((r) => ({ ...r })));
189
+ const counterSnapshot = new Map(this._autoIncrementCounters);
171
190
  try {
172
191
  const result = await fn(this);
173
192
  return result;
174
193
  } catch (err) {
175
194
  this._tables = snapshot;
195
+ this._autoIncrementCounters = counterSnapshot;
176
196
  throw err;
177
197
  }
178
198
  }
@@ -191,11 +211,14 @@ var InMemoryDatabaseAdapter = class {
191
211
  const upper = resolved.trimStart().toUpperCase();
192
212
  if (/^CREATE\s+TABLE/i.test(upper)) return this._createTable(resolved);
193
213
  if (/^DROP\s+TABLE/i.test(upper)) return this._dropTable(resolved);
214
+ if (/^CREATE\s+(?:UNIQUE\s+)?INDEX/i.test(upper)) return this._createIndex(resolved);
215
+ if (/^DROP\s+INDEX/i.test(upper)) return this._dropIndex(resolved);
194
216
  if (/^CREATE\s+(?:OR\s+REPLACE\s+)?VIEW/i.test(upper)) return [];
195
217
  if (/^INSERT\s+(?:IGNORE\s+)?INTO/i.test(upper)) return this._insert(resolved, /^INSERT\s+IGNORE\s+INTO/i.test(upper));
196
218
  if (/^SELECT/i.test(upper)) return this._select(resolved);
197
219
  if (/^UPDATE/i.test(upper)) return this._update(resolved);
198
220
  if (/^DELETE\s+FROM/i.test(upper)) return this._delete(resolved);
221
+ if (/^PRAGMA\s+table_info\s*\(/i.test(upper)) return this._pragmaTableInfo(resolved);
199
222
  return [];
200
223
  }
201
224
  /**
@@ -217,6 +240,50 @@ var InMemoryDatabaseAdapter = class {
217
240
  if (m) {
218
241
  const name = m[1];
219
242
  if (!this._tables.has(name)) this._tables.set(name, []);
243
+ const colDefsM = /\((.+)\)\s*$/is.exec(sql);
244
+ if (colDefsM) {
245
+ const colDefs = colDefsM[1];
246
+ const aiMatch = /^\s*(\w+)\s+\w+.*?(?:AUTO_INCREMENT|AUTOINCREMENT)/im.exec(colDefs);
247
+ if (aiMatch) {
248
+ this._autoIncrementCols.set(name, aiMatch[1]);
249
+ if (!this._autoIncrementCounters.has(name)) this._autoIncrementCounters.set(name, 0);
250
+ }
251
+ const colLines = this._splitTopLevelCommas(colDefs);
252
+ const pkCols = [];
253
+ for (const colLine of colLines) {
254
+ const trimmedLine = colLine.trim();
255
+ const tablePkM = /^PRIMARY\s+KEY\s*\(([^)]+)\)/i.exec(trimmedLine);
256
+ if (tablePkM) {
257
+ const cols = tablePkM[1].split(",").map((c) => c.trim().replace(/^[`"](.+)[`"]$/, "$1"));
258
+ pkCols.push(...cols);
259
+ continue;
260
+ }
261
+ const defaultM = /^\s*(\w+)\s+\S+.*?\bDEFAULT\s+(.+?)(?:\s*,?\s*$)/i.exec(trimmedLine);
262
+ if (defaultM) {
263
+ const colName = defaultM[1];
264
+ const defaultVal = defaultM[2].trim();
265
+ this._columnDefaults.set(`${name}.${colName}`, defaultVal);
266
+ }
267
+ const colPkM = /^(\w+)\s+\w+.*?\bPRIMARY\s+KEY\b/i.exec(trimmedLine);
268
+ if (colPkM && !/^PRIMARY\s+KEY\b/i.test(trimmedLine)) pkCols.push(colPkM[1]);
269
+ }
270
+ if (pkCols.length > 0 && !this._primaryKeys.has(name)) this._primaryKeys.set(name, pkCols);
271
+ if (!this._tableColumns.has(name)) {
272
+ const colNames = [];
273
+ for (const colLine of colLines) {
274
+ const trimmedLine = colLine.trim();
275
+ if (/^(?:PRIMARY\s+KEY|UNIQUE|FOREIGN\s+KEY|CHECK)\s*[\s(]/i.test(trimmedLine)) continue;
276
+ const quotedM = /^[`"](\w+)[`"]\s+\S/i.exec(trimmedLine);
277
+ if (quotedM) {
278
+ colNames.push(quotedM[1]);
279
+ continue;
280
+ }
281
+ const bareM = /^(\w+)\s+\S/i.exec(trimmedLine);
282
+ if (bareM) colNames.push(bareM[1]);
283
+ }
284
+ if (colNames.length > 0) this._tableColumns.set(name, colNames);
285
+ }
286
+ }
220
287
  }
221
288
  return [];
222
289
  }
@@ -225,16 +292,101 @@ var InMemoryDatabaseAdapter = class {
225
292
  if (m) this._tables.delete(m[1]);
226
293
  return [];
227
294
  }
295
+ _createIndex(sql) {
296
+ const m = /CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)\s+ON\s+(\w+)/i.exec(sql);
297
+ if (m) {
298
+ const name = m[1];
299
+ const tbl_name = m[2];
300
+ if (!this._indexes.some((idx) => idx.name === name)) this._indexes.push({
301
+ name,
302
+ tbl_name,
303
+ type: "index"
304
+ });
305
+ }
306
+ return [];
307
+ }
308
+ _dropIndex(sql) {
309
+ const m = /DROP\s+INDEX\s+(?:IF\s+EXISTS\s+)?(\w+)/i.exec(sql);
310
+ if (m) {
311
+ const name = m[1];
312
+ this._indexes = this._indexes.filter((idx) => idx.name !== name);
313
+ }
314
+ return [];
315
+ }
316
+ /**
317
+ * Emulate PRAGMA table_info(tableName) — returns one row per column with
318
+ * {cid, name, type, notnull, dflt_value, pk} shape.
319
+ * Column names are derived from the CREATE TABLE definition order.
320
+ * This is sufficient for schema compatibility checks.
321
+ */
322
+ _pragmaTableInfo(sql) {
323
+ const m = /PRAGMA\s+table_info\s*\(\s*[`'"]?(\w+)[`'"]?\s*\)/i.exec(sql);
324
+ if (!m) return [];
325
+ const tableName = m[1];
326
+ const colNames = this._tableColumns.get(tableName);
327
+ if (!colNames || colNames.length === 0) return [];
328
+ const pkCols = this._primaryKeys.get(tableName) ?? [];
329
+ return colNames.map((name, cid) => ({
330
+ cid,
331
+ name,
332
+ type: "TEXT",
333
+ notnull: 0,
334
+ dflt_value: null,
335
+ pk: pkCols.includes(name) ? 1 : 0
336
+ }));
337
+ }
338
+ _selectFromSqliteMaster(sql) {
339
+ const masterRows = [];
340
+ for (const name of this._tables.keys()) masterRows.push({
341
+ type: "table",
342
+ name,
343
+ tbl_name: name,
344
+ rootpage: 0,
345
+ sql: null
346
+ });
347
+ for (const idx of this._indexes) masterRows.push({
348
+ type: "index",
349
+ name: idx.name,
350
+ tbl_name: idx.tbl_name,
351
+ rootpage: 0,
352
+ sql: null
353
+ });
354
+ const whereM = /WHERE\s+(.+?)(?:\s+ORDER\s+BY|\s+LIMIT|\s*$)/is.exec(sql);
355
+ let rows = masterRows;
356
+ if (whereM) rows = rows.filter((row) => this._matchWhere(whereM[1].trim(), row));
357
+ const colsM = /SELECT\s+(.+?)\s+FROM\s+sqlite_master/is.exec(sql);
358
+ if (!colsM) return rows;
359
+ const colsStr = colsM[1].trim();
360
+ if (colsStr === "*") return rows;
361
+ return rows.map((row) => this._projectCols(colsStr, row));
362
+ }
228
363
  _insert(sql, _ignoreConflicts = false) {
229
364
  const m = /INSERT\s+(?:IGNORE\s+)?INTO\s+(\w+)\s*\(([^)]+)\)\s*VALUES\s*\((.+)\)\s*$/is.exec(sql);
230
365
  if (!m) return [];
231
366
  const tableName = m[1];
232
- const cols = m[2].split(",").map((c) => c.trim());
367
+ const cols = m[2].split(",").map((c) => c.trim().replace(/^[`"](.+)[`"]$/, "$1"));
233
368
  const valStr = m[3];
234
369
  const vals = this._parseValueList(valStr);
235
370
  const row = {};
236
371
  for (let i = 0; i < cols.length; i++) row[cols[i]] = vals[i] ?? null;
372
+ const aiCol = this._autoIncrementCols.get(tableName);
373
+ if (aiCol && !(aiCol in row)) {
374
+ const next = (this._autoIncrementCounters.get(tableName) ?? 0) + 1;
375
+ this._autoIncrementCounters.set(tableName, next);
376
+ row[aiCol] = next;
377
+ }
378
+ for (const [key, defaultExpr] of this._columnDefaults) {
379
+ const [tbl, col] = key.split(".");
380
+ if (tbl === tableName && !(col in row)) if (/^CURRENT_TIMESTAMP$/i.test(defaultExpr)) row[col] = new Date().toISOString();
381
+ else row[col] = this._evalLiteral(defaultExpr);
382
+ }
237
383
  if (!this._tables.has(tableName)) this._tables.set(tableName, []);
384
+ const pkCols = this._primaryKeys.get(tableName);
385
+ if (pkCols && pkCols.length > 0 && !_ignoreConflicts) {
386
+ const table = this._tables.get(tableName);
387
+ const isDuplicate = table.some((existingRow) => pkCols.every((col) => existingRow[col] !== void 0 && String(existingRow[col]) === String(row[col])));
388
+ if (isDuplicate) throw new Error(`UNIQUE constraint failed: ${tableName} (${pkCols.join(", ")})`);
389
+ }
238
390
  this._tables.get(tableName).push(row);
239
391
  return [];
240
392
  }
@@ -244,7 +396,20 @@ var InMemoryDatabaseAdapter = class {
244
396
  if (!m$1) return [];
245
397
  return [this._evalSelectExprs(m$1[1].trim())];
246
398
  }
247
- const stripped = sql.replace(/\s+ORDER\s+BY\s+.+?(?=\s+LIMIT\s|\s*$)/is, "").replace(/\s+LIMIT\s+\d+\s*$/is, "");
399
+ if (/FROM\s+sqlite_master/i.test(sql)) return this._selectFromSqliteMaster(sql);
400
+ let limitValue;
401
+ const limitMatch = /\s+LIMIT\s+(\d+)\s*$/is.exec(sql);
402
+ if (limitMatch) limitValue = parseInt(limitMatch[1], 10);
403
+ let orderByExprs;
404
+ const orderByMatch = /\s+ORDER\s+BY\s+(.+?)(?:\s+LIMIT\s+\d+\s*)?$/is.exec(sql);
405
+ if (orderByMatch) orderByExprs = this._parseOrderBy(orderByMatch[1].trim());
406
+ let stripped = sql.replace(/\s+ORDER\s+BY\s+.+?(?=\s+LIMIT\s|\s*$)/is, "").replace(/\s+LIMIT\s+\d+\s*$/is, "");
407
+ let groupByCols = null;
408
+ const groupByMatch = /\s+GROUP\s+BY\s+(.+?)(?:\s+HAVING\s+.+?)?$/is.exec(stripped);
409
+ if (groupByMatch) {
410
+ groupByCols = groupByMatch[1].split(",").map((c) => c.trim());
411
+ stripped = stripped.replace(/\s+GROUP\s+BY\s+.+$/is, "");
412
+ }
248
413
  const m = /SELECT\s+(.+?)\s+FROM\s+(\w+)(?:\s+WHERE\s+(.+))?$/is.exec(stripped);
249
414
  if (!m) return [];
250
415
  const colsStr = m[1].trim();
@@ -253,10 +418,201 @@ var InMemoryDatabaseAdapter = class {
253
418
  const table = this._tables.get(tableName) ?? [];
254
419
  let rows = table.map((r) => ({ ...r }));
255
420
  if (whereStr) rows = rows.filter((row) => this._matchWhere(whereStr.trim(), row));
421
+ if (groupByCols !== null) {
422
+ const grouped = this._applyGroupBy(colsStr, rows, groupByCols);
423
+ const sorted = orderByExprs ? this._applyOrderBy(grouped, orderByExprs) : grouped;
424
+ return limitValue !== void 0 ? sorted.slice(0, limitValue) : sorted;
425
+ }
426
+ if (orderByExprs) rows = this._applyOrderBy(rows, orderByExprs);
427
+ if (limitValue !== void 0) rows = rows.slice(0, limitValue);
256
428
  if (colsStr === "*") return rows;
257
429
  if (/\b(?:SUM|COALESCE|COUNT|AVG|MIN|MAX)\s*\(/i.test(colsStr)) return [this._evalAggregate(colsStr, rows)];
258
430
  return rows.map((row) => this._projectCols(colsStr, row));
259
431
  }
432
+ /**
433
+ * Apply GROUP BY: bucket rows by the group-by columns, then evaluate
434
+ * aggregate expressions for each bucket. Plain column references in the
435
+ * SELECT list that are also GROUP BY columns return the group value from
436
+ * the first row in the bucket (all rows in a group share the same value).
437
+ */
438
+ _applyGroupBy(colsStr, rows, groupByCols) {
439
+ const groups = new Map();
440
+ for (const row of rows) {
441
+ const key = groupByCols.map((col) => String(row[col] ?? "")).join("\0");
442
+ if (!groups.has(key)) groups.set(key, []);
443
+ groups.get(key).push(row);
444
+ }
445
+ const result = [];
446
+ for (const [, groupRows] of groups) result.push(this._evalAggregateGroup(colsStr, groupRows));
447
+ return result;
448
+ }
449
+ /**
450
+ * Parse ORDER BY expression list into sort keys.
451
+ * Handles: simple columns, COALESCE(col, default) DESC, CASE...END expressions.
452
+ */
453
+ _parseOrderBy(orderByStr) {
454
+ const parts = this._splitTopLevelCommas(orderByStr);
455
+ return parts.map((part) => {
456
+ const trimmed = part.trim();
457
+ const dirMatch = /^(.*?)\s+(ASC|DESC)\s*$/i.exec(trimmed);
458
+ if (dirMatch) return {
459
+ expr: dirMatch[1].trim(),
460
+ dir: dirMatch[2].toUpperCase()
461
+ };
462
+ return {
463
+ expr: trimmed,
464
+ dir: "ASC"
465
+ };
466
+ });
467
+ }
468
+ /**
469
+ * Sort rows according to ORDER BY expression list.
470
+ */
471
+ _applyOrderBy(rows, orderBy) {
472
+ return [...rows].sort((a, b) => {
473
+ for (const { expr, dir } of orderBy) {
474
+ const aVal = this._evalOrderByExpr(expr, a);
475
+ const bVal = this._evalOrderByExpr(expr, b);
476
+ const cmp = this._compareValues(aVal, bVal);
477
+ if (cmp !== 0) return dir === "DESC" ? -cmp : cmp;
478
+ }
479
+ return 0;
480
+ });
481
+ }
482
+ /**
483
+ * Evaluate an ORDER BY expression for a single row.
484
+ */
485
+ _evalOrderByExpr(expr, row) {
486
+ const trimmed = expr.trim();
487
+ const simpleCaseM = /^CASE\s+(\w+)\s+(.+?)\s+END$/is.exec(trimmed);
488
+ if (simpleCaseM) {
489
+ const col = simpleCaseM[1];
490
+ const colVal = String(row[col] ?? "");
491
+ const body = simpleCaseM[2].trim();
492
+ const whenMatches = [...body.matchAll(/WHEN\s+'([^']*)'\s+THEN\s+(\S+)/gi)];
493
+ for (const wm of whenMatches) if (colVal === wm[1]) return this._evalLiteral(wm[2]);
494
+ const elseM = /ELSE\s+(\S+)\s*$/i.exec(body);
495
+ if (elseM) return this._evalLiteral(elseM[1]);
496
+ return null;
497
+ }
498
+ return this._evalRowExpr(trimmed, row);
499
+ }
500
+ /**
501
+ * Compare two values for sorting (handles null, numbers, strings).
502
+ * Returns negative if a < b, 0 if equal, positive if a > b.
503
+ */
504
+ _compareValues(a, b) {
505
+ if (a === null || a === void 0) return b === null || b === void 0 ? 0 : 1;
506
+ if (b === null || b === void 0) return -1;
507
+ if (typeof a === "number" && typeof b === "number") return a - b;
508
+ return String(a).localeCompare(String(b));
509
+ }
510
+ /**
511
+ * Evaluate SELECT expressions against a single GROUP BY bucket.
512
+ * Handles both aggregate expressions (SUM, COUNT, COALESCE) and plain
513
+ * column references (for GROUP BY projected columns).
514
+ */
515
+ _evalAggregateGroup(colsStr, rows) {
516
+ const result = {};
517
+ const cols = this._splitTopLevelCommas(colsStr);
518
+ for (const col of cols) {
519
+ const aliasM = /^(.+?)\s+AS\s+(\w+)$/i.exec(col);
520
+ const expr = aliasM ? aliasM[1].trim() : col.trim();
521
+ const alias = aliasM ? aliasM[2] : col.trim();
522
+ result[alias] = this._evalAggregateExprGrouped(expr, rows);
523
+ }
524
+ return result;
525
+ }
526
+ /**
527
+ * Evaluate a single aggregate expression against a GROUP BY bucket.
528
+ * Extends _evalAggregateExpr with:
529
+ * - SUM(expr) where expr may be CASE WHEN ... END
530
+ * - Plain column references (returns first-row value, for GROUP BY cols)
531
+ */
532
+ _evalAggregateExprGrouped(expr, rows) {
533
+ const trimmed = expr.trim();
534
+ const coalesceM = /^COALESCE\((.+)\)$/i.exec(trimmed);
535
+ if (coalesceM) {
536
+ const args = this._splitTopLevelCommas(coalesceM[1]);
537
+ for (const arg of args) {
538
+ const val = this._evalAggregateExprGrouped(arg.trim(), rows);
539
+ if (val !== null && val !== void 0) return val;
540
+ }
541
+ return null;
542
+ }
543
+ const sumM = /^SUM\((.+)\)$/i.exec(trimmed);
544
+ if (sumM) {
545
+ if (rows.length === 0) return null;
546
+ let total = 0;
547
+ for (const row of rows) total += Number(this._evalRowExpr(sumM[1].trim(), row) ?? 0);
548
+ return total;
549
+ }
550
+ if (/^COUNT\(\*\)$/i.test(trimmed)) return rows.length;
551
+ const countColM = /^COUNT\((\w+)\)$/i.exec(trimmed);
552
+ if (countColM) {
553
+ const col = countColM[1];
554
+ return rows.filter((r) => r[col] !== null && r[col] !== void 0).length;
555
+ }
556
+ const maxColM = /^MAX\((\w+)\)$/i.exec(trimmed);
557
+ if (maxColM) {
558
+ const col = maxColM[1];
559
+ const values = rows.map((r) => r[col]).filter((v) => v !== null && v !== void 0);
560
+ if (values.length === 0) return null;
561
+ return values.reduce((a, b) => String(a) >= String(b) ? a : b);
562
+ }
563
+ const minColM = /^MIN\((\w+)\)$/i.exec(trimmed);
564
+ if (minColM) {
565
+ const col = minColM[1];
566
+ const values = rows.map((r) => r[col]).filter((v) => v !== null && v !== void 0);
567
+ if (values.length === 0) return null;
568
+ return values.reduce((a, b) => String(a) <= String(b) ? a : b);
569
+ }
570
+ if (/^\w+$/.test(trimmed) && rows.length > 0 && trimmed in rows[0]) return rows[0][trimmed];
571
+ return this._evalLiteral(trimmed);
572
+ }
573
+ /**
574
+ * Evaluate an expression for a single row.
575
+ * Supports CASE WHEN conditions, COALESCE, column references, and literals.
576
+ * Used by _evalAggregateExprGrouped to evaluate the argument of SUM().
577
+ */
578
+ _evalRowExpr(expr, row) {
579
+ const trimmed = expr.trim();
580
+ const coalesceM = /^COALESCE\((.+)\)$/i.exec(trimmed);
581
+ if (coalesceM) {
582
+ const args = this._splitTopLevelCommas(coalesceM[1]);
583
+ for (const arg of args) {
584
+ const val = this._evalRowExpr(arg.trim(), row);
585
+ if (val !== null && val !== void 0) return val;
586
+ }
587
+ return null;
588
+ }
589
+ const caseM = /^CASE\s+WHEN\s+(.+?)\s+THEN\s+(.+?)\s+ELSE\s+(.+?)\s+END$/is.exec(trimmed);
590
+ if (caseM) {
591
+ const matches = this._evalRowCondition(caseM[1].trim(), row);
592
+ return this._evalRowExpr(matches ? caseM[2].trim() : caseM[3].trim(), row);
593
+ }
594
+ if (/^\w+$/.test(trimmed) && /^[a-zA-Z_]/.test(trimmed)) {
595
+ if (trimmed in row) return row[trimmed];
596
+ return null;
597
+ }
598
+ return this._evalLiteral(trimmed);
599
+ }
600
+ /**
601
+ * Evaluate a simple condition (col = 'str', col = num) for a single row.
602
+ * Returns true if the condition holds.
603
+ */
604
+ _evalRowCondition(condition, row) {
605
+ const trimmed = condition.trim();
606
+ const strM = /^(\w+)\s*=\s*'(.*)'$/is.exec(trimmed);
607
+ if (strM) {
608
+ const colVal = String(row[strM[1]] ?? "");
609
+ const literal = strM[2].replace(/''/g, "'");
610
+ return colVal === literal;
611
+ }
612
+ const numM = /^(\w+)\s*=\s*(-?\d+(?:\.\d+)?)$/.exec(trimmed);
613
+ if (numM) return Number(row[numM[1]]) === parseFloat(numM[2]);
614
+ return false;
615
+ }
260
616
  _update(sql) {
261
617
  const m = /UPDATE\s+(\w+)\s+SET\s+(.+?)(?:\s+WHERE\s+(.+))?$/is.exec(sql);
262
618
  if (!m) return [];
@@ -265,8 +621,10 @@ var InMemoryDatabaseAdapter = class {
265
621
  const whereStr = m[3];
266
622
  const table = this._tables.get(tableName);
267
623
  if (!table) return [];
268
- const assignments = this._parseAssignments(setStr);
269
- for (const row of table) if (!whereStr || this._matchWhere(whereStr.trim(), row)) for (const [col, val] of assignments) row[col] = val;
624
+ for (const row of table) if (!whereStr || this._matchWhere(whereStr.trim(), row)) {
625
+ const assignments = this._parseAssignmentsForRow(setStr, row);
626
+ for (const [col, val] of assignments) row[col] = val;
627
+ }
270
628
  return [];
271
629
  }
272
630
  _delete(sql) {
@@ -292,29 +650,65 @@ var InMemoryDatabaseAdapter = class {
292
650
  const conditions = whereClause.split(/\s+AND\s+/i);
293
651
  for (const condition of conditions) {
294
652
  const trimmed = condition.trim();
295
- const strM = /^(\w+)\s*=\s*'(.*)'$/is.exec(trimmed);
653
+ const strM = /^[`"]?(\w+)[`"]?\s*=\s*'(.*)'$/is.exec(trimmed);
296
654
  if (strM) {
297
655
  const colVal = String(row[strM[1]] ?? "");
298
656
  const literal = strM[2].replace(/''/g, "'");
299
657
  if (colVal !== literal) return false;
300
658
  continue;
301
659
  }
302
- const numM = /^(\w+)\s*=\s*(-?\d+(?:\.\d+)?)$/.exec(trimmed);
660
+ const strNeqM = /^[`"]?(\w+)[`"]?\s*!=\s*'(.*)'$/is.exec(trimmed);
661
+ if (strNeqM) {
662
+ const colVal = String(row[strNeqM[1]] ?? "");
663
+ const literal = strNeqM[2].replace(/''/g, "'");
664
+ if (colVal === literal) return false;
665
+ continue;
666
+ }
667
+ const numM = /^[`"]?(\w+)[`"]?\s*=\s*(-?\d+(?:\.\d+)?)$/.exec(trimmed);
303
668
  if (numM) {
304
669
  if (Number(row[numM[1]]) !== parseFloat(numM[2])) return false;
305
670
  continue;
306
671
  }
307
- const nullM = /^(\w+)\s+IS\s+NULL$/i.exec(trimmed);
672
+ const numNeqM = /^[`"]?(\w+)[`"]?\s*!=\s*(-?\d+(?:\.\d+)?)$/.exec(trimmed);
673
+ if (numNeqM) {
674
+ if (Number(row[numNeqM[1]]) === parseFloat(numNeqM[2])) return false;
675
+ continue;
676
+ }
677
+ const strCmpM = /^[`"]?(\w+)[`"]?\s*(>=|<=|>|<)\s*'(.*)'$/s.exec(trimmed);
678
+ if (strCmpM) {
679
+ const colVal = row[strCmpM[1]];
680
+ if (colVal === null || colVal === void 0) return false;
681
+ const lhs = String(colVal);
682
+ const rhs = strCmpM[3].replace(/''/g, "'");
683
+ const op = strCmpM[2];
684
+ if (op === "<" && !(lhs < rhs)) return false;
685
+ if (op === "<=" && !(lhs <= rhs)) return false;
686
+ if (op === ">" && !(lhs > rhs)) return false;
687
+ if (op === ">=" && !(lhs >= rhs)) return false;
688
+ continue;
689
+ }
690
+ const numCmpM = /^[`"]?(\w+)[`"]?\s*(>=|<=|>|<)\s*(-?\d+(?:\.\d+)?)$/.exec(trimmed);
691
+ if (numCmpM) {
692
+ const colVal = Number(row[numCmpM[1]] ?? 0);
693
+ const rhs = parseFloat(numCmpM[3]);
694
+ const op = numCmpM[2];
695
+ if (op === "<" && !(colVal < rhs)) return false;
696
+ if (op === "<=" && !(colVal <= rhs)) return false;
697
+ if (op === ">" && !(colVal > rhs)) return false;
698
+ if (op === ">=" && !(colVal >= rhs)) return false;
699
+ continue;
700
+ }
701
+ const nullM = /^[`"]?(\w+)[`"]?\s+IS\s+NULL$/i.exec(trimmed);
308
702
  if (nullM) {
309
703
  if (row[nullM[1]] !== null && row[nullM[1]] !== void 0) return false;
310
704
  continue;
311
705
  }
312
- const notNullM = /^(\w+)\s+IS\s+NOT\s+NULL$/i.exec(trimmed);
706
+ const notNullM = /^[`"]?(\w+)[`"]?\s+IS\s+NOT\s+NULL$/i.exec(trimmed);
313
707
  if (notNullM) {
314
708
  if (row[notNullM[1]] === null || row[notNullM[1]] === void 0) return false;
315
709
  continue;
316
710
  }
317
- const likeM = /^(\w+)\s+LIKE\s+'(.*)'$/is.exec(trimmed);
711
+ const likeM = /^[`"]?(\w+)[`"]?\s+LIKE\s+'(.*)'$/is.exec(trimmed);
318
712
  if (likeM) {
319
713
  const colVal = row[likeM[1]];
320
714
  if (colVal === null || colVal === void 0) return false;
@@ -324,6 +718,22 @@ var InMemoryDatabaseAdapter = class {
324
718
  if (!regex.test(String(colVal))) return false;
325
719
  continue;
326
720
  }
721
+ const inM = /^[`"]?(\w+)[`"]?\s+IN\s*\((.+)\)$/is.exec(trimmed);
722
+ if (inM) {
723
+ const colVal = row[inM[1]];
724
+ const inValues = this._parseValueList(inM[2]);
725
+ const colStr = colVal === null || colVal === void 0 ? null : typeof colVal === "number" ? colVal : String(colVal);
726
+ if (!inValues.some((v) => v === colStr || String(v) === String(colStr))) return false;
727
+ continue;
728
+ }
729
+ const notInM = /^[`"]?(\w+)[`"]?\s+NOT\s+IN\s*\((.+)\)$/is.exec(trimmed);
730
+ if (notInM) {
731
+ const colVal = row[notInM[1]];
732
+ const notInValues = this._parseValueList(notInM[2]);
733
+ const colStr = colVal === null || colVal === void 0 ? null : typeof colVal === "number" ? colVal : String(colVal);
734
+ if (notInValues.some((v) => v === colStr || String(v) === String(colStr))) return false;
735
+ continue;
736
+ }
327
737
  }
328
738
  return true;
329
739
  }
@@ -333,7 +743,7 @@ var InMemoryDatabaseAdapter = class {
333
743
  for (const col of cols) {
334
744
  const aliasM = /^(.+?)\s+AS\s+(\w+)$/i.exec(col);
335
745
  if (aliasM) result[aliasM[2]] = this._evalExprAgainstRow(aliasM[1].trim(), row);
336
- else result[col] = row[col];
746
+ else result[col] = col in row ? row[col] : null;
337
747
  }
338
748
  return result;
339
749
  }
@@ -413,7 +823,7 @@ var InMemoryDatabaseAdapter = class {
413
823
  }
414
824
  /**
415
825
  * Evaluate a single aggregate expression against a set of rows.
416
- * Supports: SUM(col), COALESCE(expr, default), COUNT(*).
826
+ * Supports: SUM(expr), COALESCE(expr, default), COUNT(*), MAX(col), MIN(col).
417
827
  */
418
828
  _evalAggregateExpr(expr, rows) {
419
829
  const trimmed = expr.trim();
@@ -426,20 +836,39 @@ var InMemoryDatabaseAdapter = class {
426
836
  }
427
837
  return null;
428
838
  }
429
- const sumM = /^SUM\((\w+)\)$/i.exec(trimmed);
839
+ const sumM = /^SUM\((.+)\)$/i.exec(trimmed);
430
840
  if (sumM) {
431
- const col = sumM[1];
432
841
  if (rows.length === 0) return null;
433
842
  let total = 0;
434
- for (const row of rows) total += Number(row[col] ?? 0);
843
+ for (const row of rows) total += Number(this._evalRowExpr(sumM[1].trim(), row) ?? 0);
435
844
  return total;
436
845
  }
437
846
  if (/^COUNT\(\*\)$/i.test(trimmed)) return rows.length;
847
+ const countDistinctM = /^COUNT\(\s*DISTINCT\s+(\w+)\s*\)$/i.exec(trimmed);
848
+ if (countDistinctM) {
849
+ const col = countDistinctM[1];
850
+ const distinct = new Set(rows.map((r) => r[col]).filter((v) => v !== null && v !== void 0));
851
+ return distinct.size;
852
+ }
438
853
  const countM = /^COUNT\((\w+)\)$/i.exec(trimmed);
439
854
  if (countM) {
440
855
  const col = countM[1];
441
856
  return rows.filter((r) => r[col] !== null && r[col] !== void 0).length;
442
857
  }
858
+ const maxM = /^MAX\((\w+)\)$/i.exec(trimmed);
859
+ if (maxM) {
860
+ const col = maxM[1];
861
+ const values = rows.map((r) => r[col]).filter((v) => v !== null && v !== void 0);
862
+ if (values.length === 0) return null;
863
+ return values.reduce((a, b) => String(a) >= String(b) ? a : b);
864
+ }
865
+ const minM = /^MIN\((\w+)\)$/i.exec(trimmed);
866
+ if (minM) {
867
+ const col = minM[1];
868
+ const values = rows.map((r) => r[col]).filter((v) => v !== null && v !== void 0);
869
+ if (values.length === 0) return null;
870
+ return values.reduce((a, b) => String(a) <= String(b) ? a : b);
871
+ }
443
872
  return this._evalLiteral(trimmed);
444
873
  }
445
874
  /**
@@ -473,8 +902,9 @@ var InMemoryDatabaseAdapter = class {
473
902
  }
474
903
  /**
475
904
  * Parse `col1 = val1, col2 = val2` assignments into an array of [col, val] pairs.
905
+ * Evaluates each RHS expression against the provided row for arithmetic support.
476
906
  */
477
- _parseAssignments(setStr) {
907
+ _parseAssignmentsForRow(setStr, row) {
478
908
  const assignments = [];
479
909
  const parts = [];
480
910
  let current = "";
@@ -502,10 +932,48 @@ var InMemoryDatabaseAdapter = class {
502
932
  if (eqIdx === -1) continue;
503
933
  const col = part.slice(0, eqIdx).trim();
504
934
  const valStr = part.slice(eqIdx + 1).trim();
505
- assignments.push([col, this._evalLiteral(valStr)]);
935
+ assignments.push([col, this._evalAssignmentExpr(valStr, row)]);
506
936
  }
507
937
  return assignments;
508
938
  }
939
+ /**
940
+ * Evaluate a SET assignment RHS expression against the current row.
941
+ * Handles: simple literals, column arithmetic (col +/- val), COALESCE, CURRENT_TIMESTAMP.
942
+ */
943
+ _evalAssignmentExpr(expr, row) {
944
+ const trimmed = expr.trim();
945
+ if (/^CURRENT_TIMESTAMP$/i.test(trimmed)) return new Date().toISOString();
946
+ const coalesceM = /^COALESCE\((.+)\)$/i.exec(trimmed);
947
+ if (coalesceM) {
948
+ const args = this._splitTopLevelCommas(coalesceM[1]);
949
+ for (const arg of args) {
950
+ const val = this._evalAssignmentExpr(arg.trim(), row);
951
+ if (val !== null && val !== void 0) return val;
952
+ }
953
+ return null;
954
+ }
955
+ const arithM = /^(\w+)\s*([+\-*\/])\s*(-?\d+(?:\.\d+)?)$/.exec(trimmed);
956
+ if (arithM) {
957
+ const colName = arithM[1];
958
+ const op = arithM[2];
959
+ const num = parseFloat(arithM[3]);
960
+ const colVal = Number(row[colName] ?? 0);
961
+ switch (op) {
962
+ case "+": return colVal + num;
963
+ case "-": return colVal - num;
964
+ case "*": return colVal * num;
965
+ case "/": return num !== 0 ? colVal / num : null;
966
+ }
967
+ }
968
+ return this._evalLiteral(trimmed);
969
+ }
970
+ /**
971
+ * @deprecated Use _parseAssignmentsForRow instead.
972
+ * Kept for internal compatibility — evaluates without row context.
973
+ */
974
+ _parseAssignments(setStr) {
975
+ return this._parseAssignmentsForRow(setStr, {});
976
+ }
509
977
  };
510
978
 
511
979
  //#endregion
@@ -2908,6 +3376,7 @@ function inspectProcessTree(opts) {
2908
3376
  return parseInt(parts[0], 10) === pid && !parts[2].includes("Z");
2909
3377
  });
2910
3378
  if (isAlive) result.orchestrator_pid = pid;
3379
+ else result.pid_file_dead = true;
2911
3380
  }
2912
3381
  } catch {}
2913
3382
  if (result.orchestrator_pid === null) {
@@ -3073,7 +3542,17 @@ async function getAutoHealthData(options) {
3073
3542
  await initSchema(adapter);
3074
3543
  let run;
3075
3544
  if (runId !== void 0) run = await getPipelineRunById(adapter, runId);
3076
- else run = await getLatestRun(adapter);
3545
+ else {
3546
+ let currentRunId;
3547
+ try {
3548
+ const currentRunIdPath = join(dbRoot, ".substrate", "current-run-id");
3549
+ const content = readFileSync$1(currentRunIdPath, "utf-8").trim();
3550
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3551
+ if (UUID_RE.test(content)) currentRunId = content;
3552
+ } catch {}
3553
+ if (currentRunId !== void 0) run = await getPipelineRunById(adapter, currentRunId);
3554
+ if (run === void 0) run = await getLatestRun(adapter);
3555
+ }
3077
3556
  if (run === void 0) {
3078
3557
  const substrateDirPath$1 = join(dbRoot, ".substrate");
3079
3558
  const fallbackProcessInfo = inspectProcessTree({
@@ -3121,16 +3600,16 @@ async function getAutoHealthData(options) {
3121
3600
  }
3122
3601
  } catch {}
3123
3602
  const substrateDirPath = join(dbRoot, ".substrate");
3124
- const processInfo = inspectProcessTree({
3603
+ const processInfo = options._processInfoOverride ?? inspectProcessTree({
3125
3604
  projectRoot,
3126
3605
  substrateDirPath
3127
3606
  });
3128
3607
  let verdict = "NO_PIPELINE_RUNNING";
3129
- if (run.status === "running") if (processInfo.zombies.length > 0) verdict = "STALLED";
3130
- else if (processInfo.orchestrator_pid !== null && processInfo.child_pids.length > 0 && stalenessSeconds > DEFAULT_STALL_THRESHOLD_SECONDS) verdict = "HEALTHY";
3608
+ if (run.status === "running") if (processInfo.orchestrator_pid !== null) verdict = "HEALTHY";
3609
+ else if (processInfo.pid_file_dead === true) verdict = "STALLED";
3610
+ else if (processInfo.zombies.length > 0) verdict = "STALLED";
3131
3611
  else if (stalenessSeconds > DEFAULT_STALL_THRESHOLD_SECONDS) verdict = "STALLED";
3132
- else if (processInfo.orchestrator_pid !== null && processInfo.child_pids.length === 0 && active > 0) verdict = "STALLED";
3133
- else if (processInfo.orchestrator_pid === null && active > 0) verdict = "STALLED";
3612
+ else if (active > 0) verdict = "STALLED";
3134
3613
  else verdict = "HEALTHY";
3135
3614
  else if (run.status === "completed" || run.status === "failed" || run.status === "stopped") verdict = "NO_PIPELINE_RUNNING";
3136
3615
  const healthOutput = {
@@ -3244,4 +3723,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
3244
3723
 
3245
3724
  //#endregion
3246
3725
  export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltClient, DoltMergeConflict, DoltNotInstalled, FileStateStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, buildPipelineStatusOutput, checkDoltInstalled, createDatabaseAdapter, createDoltClient, createStateStore, detectCycles, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initSchema, initializeDolt, inspectProcessTree, isOrchestratorProcessLine, isSyncAdapter, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runHealthAction, validateStoryKey };
3247
- //# sourceMappingURL=health-Dnx-FGva.js.map
3726
+ //# sourceMappingURL=health-C-VRJruD.js.map