@runa-ai/runa-cli 0.6.0 → 0.7.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.
Files changed (71) hide show
  1. package/dist/{build-BXUJKYHC.js → build-V66FAQXB.js} +152 -163
  2. package/dist/{cache-H63JKFYH.js → cache-N7WNPEYF.js} +2 -3
  3. package/dist/check-LOMVIRHX.js +12 -0
  4. package/dist/{chunk-HPYJPB5Y.js → chunk-2APB25TT.js} +44 -10
  5. package/dist/chunk-3WDV32GA.js +33 -0
  6. package/dist/chunk-5FT3F36G.js +59 -0
  7. package/dist/{chunk-7QV7U6NI.js → chunk-6FAU4IGR.js} +2 -1
  8. package/dist/{chunk-CE3DEYFT.js → chunk-7B5C6U2K.js} +2 -208
  9. package/dist/{chunk-GOGRLQNP.js → chunk-AIP6MR42.js} +1 -1
  10. package/dist/chunk-FHG3ILE4.js +2011 -0
  11. package/dist/{chunk-22CS6EMA.js → chunk-H2AHNI75.js} +1 -1
  12. package/dist/{chunk-UU55OH7P.js → chunk-KE6QJBZG.js} +2 -3
  13. package/dist/{check-6AB5NGWK.js → chunk-QM53IQHM.js} +14 -12
  14. package/dist/{chunk-RRGQCUKT.js → chunk-WJXC4MVY.js} +30 -3
  15. package/dist/chunk-XDCHRVE3.js +215 -0
  16. package/dist/{chunk-P7U52PBY.js → chunk-Z4Z5DNW4.js} +49 -2
  17. package/dist/{ci-V3PIG2GI.js → ci-ZWRVWNFX.js} +1108 -132
  18. package/dist/cli/contract-output.d.ts +1 -0
  19. package/dist/{cli-GFRZCJQR.js → cli-2JNBJUBB.js} +216 -173
  20. package/dist/commands/build/actors/validate.d.ts +2 -0
  21. package/dist/commands/check/commands/check.d.ts +8 -3
  22. package/dist/commands/ci/machine/actors/db/collect-schema-stats.d.ts +9 -6
  23. package/dist/commands/ci/machine/actors/db/schema-canonical-diff.d.ts +55 -0
  24. package/dist/commands/ci/machine/actors/db/schema-stats.d.ts +11 -0
  25. package/dist/commands/ci/machine/actors/db/sync-schema.d.ts +9 -1
  26. package/dist/commands/ci/machine/formatters/sections/schema-matrix.d.ts +3 -3
  27. package/dist/commands/ci/machine/types.d.ts +2 -0
  28. package/dist/commands/ci/utils/execa-helpers.d.ts +1 -0
  29. package/dist/commands/db/commands/db-sync/error-classifier.d.ts +9 -0
  30. package/dist/commands/dev/actors/index.d.ts +5 -0
  31. package/dist/commands/dev/actors/tables-manifest.d.ts +16 -0
  32. package/dist/commands/dev/contract.d.ts +1 -1
  33. package/dist/commands/dev/guards.d.ts +24 -0
  34. package/dist/commands/dev/machine.d.ts +22 -3
  35. package/dist/commands/dev/types.d.ts +2 -0
  36. package/dist/commands/doctor.d.ts +9 -0
  37. package/dist/commands/inject-test-attrs/defaults.d.ts +9 -0
  38. package/dist/commands/utils/machine-state-logging.d.ts +20 -0
  39. package/dist/commands/utils/repo-root.d.ts +2 -0
  40. package/dist/{db-HR7CREX2.js → db-XULCILOU.js} +440 -2216
  41. package/dist/{dev-A7RW6XQV.js → dev-5YXNPTCJ.js} +168 -49
  42. package/dist/doctor-MZLOA53G.js +44 -0
  43. package/dist/{env-B47Z4747.js → env-SS66PZ4B.js} +6 -7
  44. package/dist/{env-files-K2C7O7L5.js → env-files-2UIUYLLR.js} +2 -2
  45. package/dist/{error-handler-4EYSDOSE.js → error-handler-HEXBRNVV.js} +2 -2
  46. package/dist/{hotfix-CULKKMGS.js → hotfix-YA3DGLOM.js} +3 -3
  47. package/dist/index.js +4 -4
  48. package/dist/{init-ELK5QCWR.js → init-ZIL6LRFO.js} +5 -6
  49. package/dist/{inject-test-attrs-Y5UD5P7Q.js → inject-test-attrs-P44BVTQS.js} +5 -18
  50. package/dist/{link-C43JRZWY.js → link-VSNDVZZD.js} +2 -3
  51. package/dist/manifest-TMFLESHW.js +19 -0
  52. package/dist/{risk-detector-BXUY2WKS.js → risk-detector-4U6ZJ2G5.js} +1 -1
  53. package/dist/{risk-detector-core-O7I7SPR7.js → risk-detector-core-TK4OAI3N.js} +2 -2
  54. package/dist/{risk-detector-plpgsql-SGMVKYJP.js → risk-detector-plpgsql-HWKS4OLR.js} +37 -7
  55. package/dist/{status-IJ4ZWHMX.js → status-UTKS63AB.js} +2 -3
  56. package/dist/{telemetry-FN7V727Y.js → telemetry-P56UBLZ2.js} +2 -3
  57. package/dist/{template-check-PNG5NQ5H.js → template-check-3P4HZXVY.js} +40 -29
  58. package/dist/{test-QYXE5UVW.js → test-V4KQL574.js} +34 -10
  59. package/dist/{test-gen-QPWOIEHU.js → test-gen-FS4CEY3P.js} +2 -3
  60. package/dist/{upgrade-3SLWVNAC.js → upgrade-NUK3ZBCL.js} +18 -6
  61. package/dist/{validate-SM4PXPS7.js → validate-CAAW4Y44.js} +2 -3
  62. package/dist/{vuln-check-TYQNEFS7.js → vuln-check-2W7N5TA2.js} +3 -4
  63. package/dist/{vuln-checker-2QXGN5YT.js → vuln-checker-IQJ56RUV.js} +413 -140
  64. package/dist/{watch-UCDVOQAH.js → watch-PNTKZYFB.js} +1 -1
  65. package/dist/{workflow-ZB5Q2PFY.js → workflow-H75N4BXX.js} +3 -4
  66. package/package.json +2 -2
  67. package/dist/chunk-JT5SUTWE.js +0 -9
  68. package/dist/chunk-M47WJJVS.js +0 -71
  69. package/dist/manifest-2NOQ2IMK.js +0 -32
  70. package/dist/{chunk-MNPMZERI.js → chunk-644FVGIQ.js} +1 -1
  71. package/dist/{chunk-XJBQINSA.js → chunk-SGJG3BKD.js} +1 -1
@@ -1,18 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- import { normalizeDatabaseUrlForDdl, enhanceConnectionError, parseBoolish, parsePostgresUrl, buildPsqlArgs, buildPsqlEnv, detectAppSchemas, formatSchemasForSql } from './chunk-CE3DEYFT.js';
4
- import { detectEnvironment } from './chunk-JMJP4A47.js';
5
- import { ensureRunaTmpDir, runLogged } from './chunk-7QV7U6NI.js';
3
+ import { normalizeDatabaseUrlForDdl, enhanceConnectionError, parseBoolish, detectAppSchemas, formatSchemasForSql } from './chunk-XDCHRVE3.js';
4
+ import { isPathContained } from './chunk-DRSUEMAK.js';
6
5
  import './chunk-QDF7QXBL.js';
7
6
  import { getSnapshotStateName, isSnapshotComplete } from './chunk-IBVVGH6X.js';
8
- import { getSafeEnv, getFilteredEnv, redactSecrets } from './chunk-II7VYQEM.js';
9
7
  import { writeEnvLocal, startAppBackground, waitForAppReady, executePrSetupBase, createErrorOutput } from './chunk-HD74F6W2.js';
8
+ import { psqlSyncQuery, parsePostgresUrl, buildPsqlArgs, buildPsqlEnv } from './chunk-7B5C6U2K.js';
9
+ import { ensureRunaTmpDir, runLogged } from './chunk-6FAU4IGR.js';
10
+ import { createMachineStateChangeLogger } from './chunk-5FT3F36G.js';
11
+ import { getSafeEnv, getFilteredEnv, redactSecrets } from './chunk-II7VYQEM.js';
10
12
  import { init_constants, detectSupabasePorts } from './chunk-VM3IWOT5.js';
11
- import { isPathContained } from './chunk-DRSUEMAK.js';
12
- import { emitJsonSuccess } from './chunk-UU55OH7P.js';
13
- import './chunk-RRGQCUKT.js';
14
- import './chunk-JT5SUTWE.js';
13
+ import { emitJsonSuccess } from './chunk-KE6QJBZG.js';
14
+ import './chunk-WJXC4MVY.js';
15
15
  import { setOutputFormat } from './chunk-HKUWEGUX.js';
16
+ import { detectEnvironment } from './chunk-JMJP4A47.js';
16
17
  import { init_esm_shims, __require } from './chunk-VRXHCR5K.js';
17
18
  import { Command } from 'commander';
18
19
  import { spawnSync, spawn, execFileSync } from 'child_process';
@@ -24,9 +25,9 @@ import { existsSync, readFileSync, readdirSync, promises, lstatSync, statSync }
24
25
  import { resolve4 } from 'dns/promises';
25
26
  import net, { isIP } from 'net';
26
27
  import { fromPromise, setup, assign, createActor } from 'xstate';
27
- import postgres from 'postgres';
28
+ import { randomUUID, createHash } from 'crypto';
28
29
  import { execa } from 'execa';
29
- import 'crypto';
30
+ import postgres from 'postgres';
30
31
  import { glob } from 'glob';
31
32
 
32
33
  createRequire(import.meta.url);
@@ -2498,9 +2499,429 @@ var applySeedsActor = fromPromise(
2498
2499
  // src/commands/ci/machine/actors/db/collect-schema-stats.ts
2499
2500
  init_esm_shims();
2500
2501
 
2501
- // src/commands/ci/machine/actors/db/schema-stats.ts
2502
+ // src/commands/ci/machine/actors/db/schema-canonical-diff.ts
2502
2503
  init_esm_shims();
2504
+ function createUnavailableCanonicalSnapshot(error) {
2505
+ return {
2506
+ available: false,
2507
+ objects: [],
2508
+ error
2509
+ };
2510
+ }
2503
2511
  var EXCLUDED_SCHEMAS = [
2512
+ "pg_catalog",
2513
+ "pg_toast",
2514
+ "information_schema",
2515
+ "auth",
2516
+ "storage",
2517
+ "realtime",
2518
+ "_realtime",
2519
+ "supabase_functions",
2520
+ "supabase_migrations",
2521
+ "vault",
2522
+ "pgsodium",
2523
+ "pgsodium_masks",
2524
+ "graphql",
2525
+ "graphql_public",
2526
+ "extensions",
2527
+ "net",
2528
+ "cron",
2529
+ "tests"
2530
+ ];
2531
+ function normalizeWhitespace(value) {
2532
+ return (value ?? "").replace(/\s+/g, " ").trim();
2533
+ }
2534
+ function stableHash(payload) {
2535
+ return createHash("sha256").update(JSON.stringify(payload)).digest("hex").slice(0, 16);
2536
+ }
2537
+ function createCanonicalObject(kind, schema, name, label, payload, parentName) {
2538
+ return {
2539
+ kind,
2540
+ schema,
2541
+ name,
2542
+ key: `${kind}:${schema}:${name}`,
2543
+ label,
2544
+ parentName,
2545
+ signature: stableHash(payload)
2546
+ };
2547
+ }
2548
+ function buildSchemaListSql(schemaNames) {
2549
+ return schemaNames.map((schemaName) => `'${schemaName}'`).join(", ");
2550
+ }
2551
+ async function getUserSchemas(sql) {
2552
+ const excludeList = buildSchemaListSql(EXCLUDED_SCHEMAS);
2553
+ const rows = await sql`
2554
+ SELECT nspname
2555
+ FROM pg_namespace
2556
+ WHERE nspname NOT IN (${sql.unsafe(excludeList)})
2557
+ AND nspname NOT LIKE 'pg_%'
2558
+ ORDER BY nspname
2559
+ `;
2560
+ return rows.map((row) => row.nspname);
2561
+ }
2562
+ async function queryTableColumns(sql, schemaList) {
2563
+ return await sql`
2564
+ SELECT
2565
+ n.nspname AS schema_name,
2566
+ c.relname AS table_name,
2567
+ a.attnum::int AS attnum,
2568
+ a.attname AS column_name,
2569
+ format_type(a.atttypid, a.atttypmod) AS data_type,
2570
+ a.attnotnull AS not_null,
2571
+ pg_get_expr(ad.adbin, ad.adrelid, true) AS default_expr,
2572
+ NULLIF(a.attidentity, '') AS identity_kind,
2573
+ NULLIF(a.attgenerated, '') AS generated_kind
2574
+ FROM pg_class c
2575
+ JOIN pg_namespace n ON n.oid = c.relnamespace
2576
+ JOIN pg_attribute a ON a.attrelid = c.oid
2577
+ LEFT JOIN pg_attrdef ad ON ad.adrelid = c.oid AND ad.adnum = a.attnum
2578
+ WHERE n.nspname IN (${sql.unsafe(schemaList)})
2579
+ AND c.relkind IN ('r', 'p')
2580
+ AND a.attnum > 0
2581
+ AND NOT a.attisdropped
2582
+ ORDER BY n.nspname, c.relname, a.attnum
2583
+ `;
2584
+ }
2585
+ async function queryTableFlags(sql, schemaList) {
2586
+ return await sql`
2587
+ SELECT
2588
+ n.nspname AS schema_name,
2589
+ c.relname AS table_name,
2590
+ c.relrowsecurity AS rls_enabled,
2591
+ c.relforcerowsecurity AS rls_forced
2592
+ FROM pg_class c
2593
+ JOIN pg_namespace n ON n.oid = c.relnamespace
2594
+ WHERE n.nspname IN (${sql.unsafe(schemaList)})
2595
+ AND c.relkind IN ('r', 'p')
2596
+ `;
2597
+ }
2598
+ async function queryTableConstraints(sql, schemaList) {
2599
+ return await sql`
2600
+ SELECT
2601
+ n.nspname AS schema_name,
2602
+ c.relname AS table_name,
2603
+ con.conname AS constraint_name,
2604
+ con.contype AS constraint_type,
2605
+ pg_get_constraintdef(con.oid, true) AS definition
2606
+ FROM pg_constraint con
2607
+ JOIN pg_class c ON c.oid = con.conrelid
2608
+ JOIN pg_namespace n ON n.oid = c.relnamespace
2609
+ WHERE n.nspname IN (${sql.unsafe(schemaList)})
2610
+ AND c.relkind IN ('r', 'p')
2611
+ ORDER BY n.nspname, c.relname, con.conname
2612
+ `;
2613
+ }
2614
+ function buildTableObjects(columns, flags, constraints) {
2615
+ const tables = /* @__PURE__ */ new Map();
2616
+ for (const row of columns) {
2617
+ const key = `${row.schema_name}.${row.table_name}`;
2618
+ const entry = tables.get(key) ?? {
2619
+ schema: row.schema_name,
2620
+ table: row.table_name,
2621
+ columns: [],
2622
+ constraints: [],
2623
+ rlsEnabled: false,
2624
+ rlsForced: false
2625
+ };
2626
+ entry.columns.push({
2627
+ attnum: row.attnum,
2628
+ name: row.column_name,
2629
+ type: normalizeWhitespace(row.data_type),
2630
+ notNull: row.not_null,
2631
+ default: normalizeWhitespace(row.default_expr),
2632
+ identity: row.identity_kind ?? "",
2633
+ generated: row.generated_kind ?? ""
2634
+ });
2635
+ tables.set(key, entry);
2636
+ }
2637
+ for (const row of flags) {
2638
+ const key = `${row.schema_name}.${row.table_name}`;
2639
+ const entry = tables.get(key) ?? {
2640
+ schema: row.schema_name,
2641
+ table: row.table_name,
2642
+ columns: [],
2643
+ constraints: [],
2644
+ rlsEnabled: false,
2645
+ rlsForced: false
2646
+ };
2647
+ entry.rlsEnabled = row.rls_enabled;
2648
+ entry.rlsForced = row.rls_forced;
2649
+ tables.set(key, entry);
2650
+ }
2651
+ for (const row of constraints) {
2652
+ const key = `${row.schema_name}.${row.table_name}`;
2653
+ const entry = tables.get(key) ?? {
2654
+ schema: row.schema_name,
2655
+ table: row.table_name,
2656
+ columns: [],
2657
+ constraints: [],
2658
+ rlsEnabled: false,
2659
+ rlsForced: false
2660
+ };
2661
+ entry.constraints.push({
2662
+ name: row.constraint_name,
2663
+ type: row.constraint_type,
2664
+ definition: normalizeWhitespace(row.definition)
2665
+ });
2666
+ tables.set(key, entry);
2667
+ }
2668
+ return Array.from(tables.values()).map(
2669
+ (table) => createCanonicalObject("table", table.schema, table.table, `${table.schema}.${table.table}`, {
2670
+ columns: table.columns,
2671
+ constraints: table.constraints,
2672
+ rlsEnabled: table.rlsEnabled,
2673
+ rlsForced: table.rlsForced
2674
+ })
2675
+ );
2676
+ }
2677
+ async function queryFunctions(sql, schemaList) {
2678
+ return await sql`
2679
+ SELECT
2680
+ n.nspname AS schema_name,
2681
+ p.proname AS object_name,
2682
+ format('%I.%I(%s)', n.nspname, p.proname, pg_get_function_identity_arguments(p.oid)) AS object_key,
2683
+ format('%I.%I(%s)', n.nspname, p.proname, pg_get_function_identity_arguments(p.oid)) AS object_label,
2684
+ pg_get_functiondef(p.oid) AS definition
2685
+ FROM pg_proc p
2686
+ JOIN pg_namespace n ON p.pronamespace = n.oid
2687
+ WHERE n.nspname IN (${sql.unsafe(schemaList)})
2688
+ AND p.prokind = 'f'
2689
+ AND NOT EXISTS (
2690
+ SELECT 1 FROM pg_depend d
2691
+ WHERE d.objid = p.oid
2692
+ AND d.deptype = 'e'
2693
+ )
2694
+ AND p.proname NOT LIKE 'supabase_%'
2695
+ AND p.proname NOT LIKE 'postgrest_%'
2696
+ AND p.proname NOT LIKE 'pgrst_%'
2697
+ AND p.proname NOT LIKE '_pgrst_%'
2698
+ ORDER BY n.nspname, p.proname
2699
+ `;
2700
+ }
2701
+ async function queryPolicies(sql, schemaList) {
2702
+ return await sql`
2703
+ SELECT
2704
+ schemaname AS schema_name,
2705
+ policyname AS object_name,
2706
+ format('%I.%I.%I', schemaname, tablename, policyname) AS object_key,
2707
+ format('%I.%I policy %I', schemaname, tablename, policyname) AS object_label,
2708
+ tablename AS parent_name,
2709
+ jsonb_build_object(
2710
+ 'table', tablename,
2711
+ 'permissive', permissive,
2712
+ 'roles', COALESCE(roles, ARRAY[]::text[]),
2713
+ 'command', cmd,
2714
+ 'using', COALESCE(qual, ''),
2715
+ 'withCheck', COALESCE(with_check, '')
2716
+ )::text AS definition
2717
+ FROM pg_policies
2718
+ WHERE schemaname IN (${sql.unsafe(schemaList)})
2719
+ ORDER BY schemaname, tablename, policyname
2720
+ `;
2721
+ }
2722
+ async function queryIndexes(sql, schemaList) {
2723
+ return await sql`
2724
+ SELECT
2725
+ n.nspname AS schema_name,
2726
+ i.relname AS object_name,
2727
+ format('%I.%I', n.nspname, i.relname) AS object_key,
2728
+ format('%I.%I on %I', n.nspname, i.relname, t.relname) AS object_label,
2729
+ t.relname AS parent_name,
2730
+ jsonb_build_object(
2731
+ 'table', t.relname,
2732
+ 'definition',
2733
+ regexp_replace(pg_get_indexdef(i.oid), '^CREATE( UNIQUE)? INDEX [^ ]+ ON ', 'CREATE\\1 INDEX ON '),
2734
+ 'predicate',
2735
+ COALESCE(pg_get_expr(idx.indpred, idx.indrelid, true), ''),
2736
+ 'unique',
2737
+ idx.indisunique
2738
+ )::text AS definition
2739
+ FROM pg_index idx
2740
+ JOIN pg_class i ON i.oid = idx.indexrelid
2741
+ JOIN pg_class t ON t.oid = idx.indrelid
2742
+ JOIN pg_namespace n ON n.oid = i.relnamespace
2743
+ WHERE n.nspname IN (${sql.unsafe(schemaList)})
2744
+ AND NOT EXISTS (
2745
+ SELECT 1 FROM pg_constraint c
2746
+ WHERE c.conindid = idx.indexrelid
2747
+ )
2748
+ ORDER BY n.nspname, t.relname, i.relname
2749
+ `;
2750
+ }
2751
+ async function queryTriggers(sql, schemaList) {
2752
+ return await sql`
2753
+ SELECT
2754
+ n.nspname AS schema_name,
2755
+ t.tgname AS object_name,
2756
+ format('%I.%I.%I', n.nspname, c.relname, t.tgname) AS object_key,
2757
+ format('%I.%I trigger %I', n.nspname, c.relname, t.tgname) AS object_label,
2758
+ c.relname AS parent_name,
2759
+ pg_get_triggerdef(t.oid, true) AS definition
2760
+ FROM pg_trigger t
2761
+ JOIN pg_class c ON t.tgrelid = c.oid
2762
+ JOIN pg_namespace n ON c.relnamespace = n.oid
2763
+ WHERE n.nspname IN (${sql.unsafe(schemaList)})
2764
+ AND NOT t.tgisinternal
2765
+ ORDER BY n.nspname, c.relname, t.tgname
2766
+ `;
2767
+ }
2768
+ function rowsToObjects(kind, rows) {
2769
+ return rows.map(
2770
+ (row) => createCanonicalObject(
2771
+ kind,
2772
+ row.schema_name,
2773
+ row.object_key,
2774
+ row.object_label,
2775
+ normalizeWhitespace(row.definition),
2776
+ row.parent_name ?? void 0
2777
+ )
2778
+ );
2779
+ }
2780
+ async function getCanonicalSchemaSnapshot(databaseUrl) {
2781
+ let sql = null;
2782
+ try {
2783
+ const isRemoteSupabase = databaseUrl.includes(".supabase.co");
2784
+ sql = postgres(databaseUrl, {
2785
+ max: 1,
2786
+ idle_timeout: 10,
2787
+ connect_timeout: 10,
2788
+ ...isRemoteSupabase && { ssl: "require" }
2789
+ });
2790
+ const userSchemas = await getUserSchemas(sql);
2791
+ if (userSchemas.length === 0) {
2792
+ return { available: true, objects: [] };
2793
+ }
2794
+ const schemaList = buildSchemaListSql(userSchemas);
2795
+ const [columns, flags, constraints, functions, policies, indexes, triggers] = await Promise.all(
2796
+ [
2797
+ queryTableColumns(sql, schemaList),
2798
+ queryTableFlags(sql, schemaList),
2799
+ queryTableConstraints(sql, schemaList),
2800
+ queryFunctions(sql, schemaList),
2801
+ queryPolicies(sql, schemaList),
2802
+ queryIndexes(sql, schemaList),
2803
+ queryTriggers(sql, schemaList)
2804
+ ]
2805
+ );
2806
+ return {
2807
+ available: true,
2808
+ objects: [
2809
+ ...buildTableObjects(columns, flags, constraints),
2810
+ ...rowsToObjects("function", functions),
2811
+ ...rowsToObjects("policy", policies),
2812
+ ...rowsToObjects("index", indexes),
2813
+ ...rowsToObjects("trigger", triggers)
2814
+ ].sort((a, b) => a.key.localeCompare(b.key))
2815
+ };
2816
+ } catch (error) {
2817
+ const message = error instanceof Error ? error.message : String(error);
2818
+ console.warn(`[schema-canonical-diff] Could not query canonical snapshot: ${message}`);
2819
+ return createUnavailableCanonicalSnapshot(message);
2820
+ } finally {
2821
+ if (sql) {
2822
+ await sql.end();
2823
+ }
2824
+ }
2825
+ }
2826
+ function buildObjectMap(snapshot) {
2827
+ return new Map(snapshot.objects.map((object) => [object.key, object]));
2828
+ }
2829
+ function buildSignatureBuckets(objects) {
2830
+ const buckets = /* @__PURE__ */ new Map();
2831
+ for (const object of objects) {
2832
+ const bucketKey = `${object.kind}:${object.schema}:${object.signature}`;
2833
+ const current = buckets.get(bucketKey) ?? [];
2834
+ current.push(object);
2835
+ buckets.set(bucketKey, current);
2836
+ }
2837
+ return buckets;
2838
+ }
2839
+ function compareCanonicalSnapshots(reference, target) {
2840
+ const missing = [];
2841
+ const extra = [];
2842
+ const changed = [];
2843
+ const renamed = [];
2844
+ const targetByKey = buildObjectMap(target);
2845
+ const targetSignatureBuckets = buildSignatureBuckets(target.objects);
2846
+ const matchedTargetKeys = /* @__PURE__ */ new Set();
2847
+ for (const referenceObject of reference.objects) {
2848
+ const matchedTarget = targetByKey.get(referenceObject.key);
2849
+ if (matchedTarget) {
2850
+ matchedTargetKeys.add(matchedTarget.key);
2851
+ if (matchedTarget.signature !== referenceObject.signature) {
2852
+ changed.push({ reference: referenceObject, target: matchedTarget });
2853
+ }
2854
+ continue;
2855
+ }
2856
+ if (referenceObject.kind === "index") {
2857
+ const renameBucket = targetSignatureBuckets.get(
2858
+ `${referenceObject.kind}:${referenceObject.schema}:${referenceObject.signature}`
2859
+ ) ?? [];
2860
+ const renamedTarget = renameBucket.find((candidate) => !matchedTargetKeys.has(candidate.key));
2861
+ if (renamedTarget) {
2862
+ matchedTargetKeys.add(renamedTarget.key);
2863
+ renamed.push({ reference: referenceObject, target: renamedTarget });
2864
+ continue;
2865
+ }
2866
+ }
2867
+ missing.push(referenceObject);
2868
+ }
2869
+ for (const targetObject of target.objects) {
2870
+ if (!matchedTargetKeys.has(targetObject.key) && !reference.objects.some((candidate) => candidate.key === targetObject.key)) {
2871
+ extra.push(targetObject);
2872
+ }
2873
+ }
2874
+ return { missing, extra, changed, renamed };
2875
+ }
2876
+ function addSummaryKind(summary, kind) {
2877
+ if (!summary.changedKinds.includes(kind)) {
2878
+ summary.changedKinds.push(kind);
2879
+ summary.changedKinds.sort();
2880
+ }
2881
+ summary.hasChanges = true;
2882
+ }
2883
+ function ensureSchemaSummary(summaries, schemaName) {
2884
+ const existing = summaries[schemaName];
2885
+ if (existing) return existing;
2886
+ const created = {
2887
+ hasChanges: false,
2888
+ changedKinds: [],
2889
+ counts: { missing: 0, extra: 0, changed: 0, renamed: 0 }
2890
+ };
2891
+ summaries[schemaName] = created;
2892
+ return created;
2893
+ }
2894
+ function summarizeCanonicalDiffBySchema(diff) {
2895
+ const summaries = {};
2896
+ for (const object of diff.missing) {
2897
+ const summary = ensureSchemaSummary(summaries, object.schema);
2898
+ summary.counts.missing += 1;
2899
+ addSummaryKind(summary, object.kind);
2900
+ }
2901
+ for (const object of diff.extra) {
2902
+ const summary = ensureSchemaSummary(summaries, object.schema);
2903
+ summary.counts.extra += 1;
2904
+ addSummaryKind(summary, object.kind);
2905
+ }
2906
+ for (const pair of diff.changed) {
2907
+ const summary = ensureSchemaSummary(summaries, pair.reference.schema);
2908
+ summary.counts.changed += 1;
2909
+ addSummaryKind(summary, pair.reference.kind);
2910
+ }
2911
+ for (const pair of diff.renamed) {
2912
+ const summary = ensureSchemaSummary(summaries, pair.reference.schema);
2913
+ summary.counts.renamed += 1;
2914
+ addSummaryKind(summary, pair.reference.kind);
2915
+ }
2916
+ return summaries;
2917
+ }
2918
+ function hasCanonicalChanges(diff) {
2919
+ return diff.missing.length > 0 || diff.extra.length > 0 || diff.changed.length > 0 || diff.renamed.length > 0;
2920
+ }
2921
+
2922
+ // src/commands/ci/machine/actors/db/schema-stats.ts
2923
+ init_esm_shims();
2924
+ var EXCLUDED_SCHEMAS2 = [
2504
2925
  // PostgreSQL system schemas
2505
2926
  "pg_catalog",
2506
2927
  "pg_toast",
@@ -2604,15 +3025,18 @@ async function getDetailedIndexList(sql, schemaList) {
2604
3025
  t.relname as table,
2605
3026
  idx.indisunique as is_unique,
2606
3027
  idx.indpred IS NOT NULL as is_partial,
3028
+ am.amname as access_method,
2607
3029
  CASE
2608
3030
  WHEN idx.indpred IS NOT NULL
2609
3031
  THEN pg_get_expr(idx.indpred, idx.indrelid, true)
2610
3032
  ELSE NULL
2611
- END as where_clause
3033
+ END as where_clause,
3034
+ pg_get_indexdef(i.oid) as definition
2612
3035
  FROM pg_index idx
2613
3036
  JOIN pg_class i ON i.oid = idx.indexrelid
2614
3037
  JOIN pg_class t ON t.oid = idx.indrelid
2615
3038
  JOIN pg_namespace n ON n.oid = i.relnamespace
3039
+ JOIN pg_am am ON am.oid = i.relam
2616
3040
  WHERE n.nspname IN (${sql.unsafe(schemaList)})
2617
3041
  AND NOT EXISTS (
2618
3042
  SELECT 1 FROM pg_constraint c
@@ -2626,11 +3050,13 @@ async function getDetailedIndexList(sql, schemaList) {
2626
3050
  table: row.table,
2627
3051
  isUnique: row.is_unique,
2628
3052
  isPartial: row.is_partial,
2629
- whereClause: row.where_clause || void 0
3053
+ whereClause: row.where_clause || void 0,
3054
+ accessMethod: row.access_method || void 0,
3055
+ definition: normalizeIndexDefinition(row.definition || void 0)
2630
3056
  }));
2631
3057
  }
2632
- async function getUserSchemas(sql) {
2633
- const excludeList = EXCLUDED_SCHEMAS.map((s) => `'${s}'`).join(", ");
3058
+ async function getUserSchemas2(sql) {
3059
+ const excludeList = EXCLUDED_SCHEMAS2.map((s) => `'${s}'`).join(", ");
2634
3060
  const result = await sql`
2635
3061
  SELECT nspname
2636
3062
  FROM pg_namespace
@@ -2653,7 +3079,7 @@ async function getDbSchemaStats(databaseUrl) {
2653
3079
  // Supabase production requires SSL; local Docker does not
2654
3080
  ...isRemoteSupabase && { ssl: "require" }
2655
3081
  });
2656
- const userSchemas = await getUserSchemas(sql);
3082
+ const userSchemas = await getUserSchemas2(sql);
2657
3083
  if (userSchemas.length === 0) {
2658
3084
  return { schemas, total };
2659
3085
  }
@@ -2783,12 +3209,30 @@ function collectExpectedDriftReasons(schemaName, fieldDiffs, expectedDrift) {
2783
3209
  function compareIndexLists(reference, target) {
2784
3210
  const refIndexes = reference.indexList || [];
2785
3211
  const targetIndexes = target.indexList || [];
2786
- const refKeys = new Set(refIndexes.map((i) => `${i.schema}.${i.name}`));
2787
- const targetKeys = new Set(targetIndexes.map((i) => `${i.schema}.${i.name}`));
2788
- const missing = refIndexes.filter((i) => !targetKeys.has(`${i.schema}.${i.name}`));
2789
- const extra = targetIndexes.filter((i) => !refKeys.has(`${i.schema}.${i.name}`));
3212
+ const buildIndexKey = (index) => [
3213
+ index.schema,
3214
+ index.table,
3215
+ index.isUnique ? "unique" : "nonunique",
3216
+ index.isPartial ? "partial" : "full",
3217
+ normalizeDiffValue(index.whereClause),
3218
+ normalizeDiffValue(index.accessMethod),
3219
+ normalizeIndexDefinition(index.definition)
3220
+ ].join(":");
3221
+ const refKeys = new Set(refIndexes.map(buildIndexKey));
3222
+ const targetKeys = new Set(targetIndexes.map(buildIndexKey));
3223
+ const missing = refIndexes.filter((index) => !targetKeys.has(buildIndexKey(index)));
3224
+ const extra = targetIndexes.filter((index) => !refKeys.has(buildIndexKey(index)));
2790
3225
  return { missing, extra };
2791
3226
  }
3227
+ function normalizeDiffValue(value) {
3228
+ return (value ?? "").replace(/\s+/g, " ").trim();
3229
+ }
3230
+ function normalizeIndexDefinition(value) {
3231
+ return normalizeDiffValue(value).replace(
3232
+ /^create\s+(unique\s+)?index\s+\S+\s+on\s+/i,
3233
+ (_match, unique = "") => `create ${unique || ""}index on `
3234
+ );
3235
+ }
2792
3236
  function formatIndexInfo(index) {
2793
3237
  const qualifiedName = `${index.schema}.${index.name}`;
2794
3238
  const tags = [];
@@ -2805,7 +3249,16 @@ function hasIndexDiff(diff) {
2805
3249
  function emptyStats() {
2806
3250
  return {
2807
3251
  schemas: {},
2808
- total: { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 }
3252
+ total: { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 },
3253
+ available: true
3254
+ };
3255
+ }
3256
+ function unavailableStats(error) {
3257
+ return {
3258
+ ...emptyStats(),
3259
+ available: false,
3260
+ error,
3261
+ canonicalSnapshot: createUnavailableCanonicalSnapshot(error)
2809
3262
  };
2810
3263
  }
2811
3264
  function logSchemaStats(label, stats, includeSchemas = false) {
@@ -2817,42 +3270,117 @@ function logSchemaStats(label, stats, includeSchemas = false) {
2817
3270
  const schemas = Object.keys(stats.schemas);
2818
3271
  console.log(`${base} (schemas: ${schemas.join(", ")})`);
2819
3272
  }
2820
- function createStatsTask(dbUrl, label, includeSchemas = false) {
3273
+ async function collectEnvironmentStats(dbUrl, label, includeSchemas = false) {
2821
3274
  if (!dbUrl) {
2822
- return Promise.resolve(null);
3275
+ return null;
2823
3276
  }
2824
- return getDbSchemaStats(dbUrl).then((stats) => {
2825
- logSchemaStats(label, stats, includeSchemas);
2826
- return stats;
2827
- });
3277
+ const stats = await getDbSchemaStats(dbUrl);
3278
+ stats.canonicalSnapshot = await getCanonicalSchemaSnapshot(dbUrl);
3279
+ stats.available = true;
3280
+ logSchemaStats(label, stats, includeSchemas);
3281
+ return stats;
2828
3282
  }
2829
3283
  function resolveSettledStats(result, failureLabel) {
2830
3284
  if (result.status === "fulfilled" && result.value) {
2831
3285
  return result.value;
2832
3286
  }
3287
+ const reason = result.status === "rejected" ? String(result.reason) : `${failureLabel} database URL is not available for schema comparison`;
2833
3288
  if (result.status === "rejected") {
2834
3289
  console.warn(`[schema-stats] Failed to query ${failureLabel} database: ${result.reason}`);
2835
3290
  }
2836
- return emptyStats();
3291
+ return unavailableStats(reason);
2837
3292
  }
2838
3293
  function resolveSettledProductionStats(result) {
2839
3294
  if (result.status === "fulfilled") {
2840
3295
  return result.value;
2841
3296
  }
2842
3297
  console.warn(`[schema-stats] Failed to query production database: ${result.reason}`);
2843
- return null;
3298
+ return unavailableStats(String(result.reason));
3299
+ }
3300
+ function buildAdminDsn(sourceDbUrl) {
3301
+ const conn = parsePostgresUrl(sourceDbUrl);
3302
+ const password = conn.password ? `:${encodeURIComponent(conn.password)}` : "";
3303
+ return `postgresql://${conn.user}${password}@${conn.host}:${conn.port}/postgres`;
3304
+ }
3305
+ function buildShadowDsn(sourceDbUrl, dbName) {
3306
+ const conn = parsePostgresUrl(sourceDbUrl);
3307
+ const password = conn.password ? `:${encodeURIComponent(conn.password)}` : "";
3308
+ return `postgresql://${conn.user}${password}@${conn.host}:${conn.port}/${dbName}`;
3309
+ }
3310
+ function createReferenceDb(sourceDbUrl) {
3311
+ const dbName = `ci_schema_ref_${randomUUID().replace(/-/g, "").slice(0, 12)}`;
3312
+ const adminDsn = buildAdminDsn(sourceDbUrl);
3313
+ const createResult = psqlSyncQuery({
3314
+ databaseUrl: adminDsn,
3315
+ sql: `CREATE DATABASE ${dbName}`,
3316
+ timeout: 3e4
3317
+ });
3318
+ if (createResult.status !== 0) {
3319
+ throw new Error(
3320
+ `Failed to create reference database: ${createResult.stderr || createResult.stdout}`
3321
+ );
3322
+ }
3323
+ return { dsn: buildShadowDsn(sourceDbUrl, dbName), dbName };
3324
+ }
3325
+ function dropReferenceDb(sourceDbUrl, dbName) {
3326
+ const adminDsn = buildAdminDsn(sourceDbUrl);
3327
+ psqlSyncQuery({
3328
+ databaseUrl: adminDsn,
3329
+ sql: `
3330
+ SELECT pg_terminate_backend(pid)
3331
+ FROM pg_stat_activity
3332
+ WHERE datname = '${dbName}' AND pid <> pg_backend_pid()
3333
+ `,
3334
+ timeout: 1e4
3335
+ });
3336
+ psqlSyncQuery({
3337
+ databaseUrl: adminDsn,
3338
+ sql: `DROP DATABASE IF EXISTS ${dbName}`,
3339
+ timeout: 3e4
3340
+ });
3341
+ }
3342
+ async function buildReferenceStats(repoRoot, tmpDir, sourceDbUrl) {
3343
+ if (!sourceDbUrl) return null;
3344
+ const referenceDb = createReferenceDb(sourceDbUrl);
3345
+ const logFile = path4.join(tmpDir, "ci-reference-db-apply.log");
3346
+ try {
3347
+ const result = await execa(
3348
+ "pnpm",
3349
+ ["exec", "runa", "db", "apply", "preview", "--auto-approve", "--no-seed", "--verbose"],
3350
+ {
3351
+ cwd: repoRoot,
3352
+ env: {
3353
+ ...process.env,
3354
+ DATABASE_URL_ADMIN: referenceDb.dsn,
3355
+ DATABASE_URL: referenceDb.dsn
3356
+ },
3357
+ reject: false
3358
+ }
3359
+ );
3360
+ const fullOutput = `${result.stdout || ""}
3361
+ ${result.stderr || ""}`;
3362
+ if (result.exitCode !== 0) {
3363
+ throw new Error(`Failed to build reference DB: ${fullOutput.substring(0, 1e3)}`);
3364
+ }
3365
+ await (await import('fs/promises')).writeFile(logFile, fullOutput);
3366
+ return collectEnvironmentStats(referenceDb.dsn, "Reference", true);
3367
+ } finally {
3368
+ dropReferenceDb(sourceDbUrl, referenceDb.dbName);
3369
+ }
2844
3370
  }
2845
3371
  var collectSchemaStatsActor = fromPromise(
2846
3372
  async ({ input }) => {
2847
- const { localDbUrl, ciDbUrl, queryProduction } = input;
2848
- console.log("[schema-stats] Collecting schema statistics (DB query mode, parallel)...");
3373
+ const { repoRoot, referenceDbUrl, ciDbUrl, queryProduction, tmpDir } = input;
3374
+ console.log(
3375
+ "[schema-stats] Collecting schema statistics (Reference/CI/Production, parallel)..."
3376
+ );
2849
3377
  const productionUrl = process.env.GH_DATABASE_URL_ADMIN || process.env.GH_DATABASE_URL;
2850
- const [localResult, ciResult, productionResult] = await Promise.allSettled([
2851
- createStatsTask(localDbUrl, "Local", true),
2852
- createStatsTask(ciDbUrl, "CI"),
2853
- createStatsTask(queryProduction ? productionUrl ?? null : null, "Production")
3378
+ const [referenceResult, ciResult, productionResult] = await Promise.allSettled([
3379
+ buildReferenceStats(repoRoot, tmpDir, referenceDbUrl),
3380
+ collectEnvironmentStats(ciDbUrl, "CI"),
3381
+ collectEnvironmentStats(queryProduction ? productionUrl ?? null : null, "Production")
2854
3382
  ]);
2855
- const local = resolveSettledStats(localResult, "local");
3383
+ const local = resolveSettledStats(referenceResult, "reference");
2856
3384
  const ci = resolveSettledStats(ciResult, "CI");
2857
3385
  const production = resolveSettledProductionStats(productionResult);
2858
3386
  return {
@@ -3611,6 +4139,25 @@ function parseSchemaChangeStats(sqlInput, logPath, repoRoot) {
3611
4139
  stats.hazards = parseHazards(sql, idempotentRoles);
3612
4140
  return stats;
3613
4141
  }
4142
+ function isCheckSummaryOutput(output) {
4143
+ return output.includes("check complete") || output.includes("Check Summary");
4144
+ }
4145
+ function isSchemaOutOfSyncOutput(output) {
4146
+ const lowerOutput = output.toLowerCase();
4147
+ return lowerOutput.includes("database schema is out of sync") || lowerOutput.includes("db_schema_out_of_sync");
4148
+ }
4149
+ function countResidualStatements(stats) {
4150
+ return stats.creates.tables + stats.creates.indexes + stats.creates.policies + stats.creates.functions + stats.creates.other + stats.alters + stats.drops;
4151
+ }
4152
+ function analyzePostCheckResult(params) {
4153
+ const hasSchemaOutOfSync = isSchemaOutOfSyncOutput(params.output);
4154
+ const hasDrift = hasSchemaOutOfSync || countResidualStatements(params.residualStats) > 0;
4155
+ const checkSucceeded = params.exitCode === 0 || isCheckSummaryOutput(params.output) || hasSchemaOutOfSync;
4156
+ return {
4157
+ hasDrift: hasDrift || params.residualStats.hazards.length > 0,
4158
+ commandFailed: !checkSucceeded
4159
+ };
4160
+ }
3614
4161
  var syncSchemaActor = fromPromise(
3615
4162
  async ({ input }) => {
3616
4163
  const { repoRoot, tmpDir, databaseUrl, mode, skipCodegen } = input;
@@ -3649,7 +4196,18 @@ var syncSchemaActor = fromPromise(
3649
4196
  // Always verbose for full traceability
3650
4197
  ...skipCodegen ? ["--skip-codegen"] : []
3651
4198
  ];
4199
+ const checkArgs = useDbApply ? ["exec", "runa", "db", "apply", envArg, "--check", "--no-seed", "--verbose"] : [
4200
+ "exec",
4201
+ "runa",
4202
+ "db",
4203
+ "sync",
4204
+ envArg,
4205
+ "--check",
4206
+ "--verbose",
4207
+ ...skipCodegen ? ["--skip-codegen"] : []
4208
+ ];
3652
4209
  const logFile = path4.join(tmpDir, `ci-db-${useDbApply ? "apply" : "sync"}-${envArg}.log`);
4210
+ const afterCheckLogFile = path4.join(tmpDir, `ci-db-check-${envArg}.log`);
3653
4211
  await runLogged({
3654
4212
  cwd: repoRoot,
3655
4213
  env: baseEnv,
@@ -3658,23 +4216,54 @@ var syncSchemaActor = fromPromise(
3658
4216
  args: syncArgs,
3659
4217
  logFile
3660
4218
  });
4219
+ const postCheckResult = await runLogged({
4220
+ cwd: repoRoot,
4221
+ env: baseEnv,
4222
+ label: `db ${useDbApply ? "apply" : "sync"} check (${envArg})`,
4223
+ command: "pnpm",
4224
+ args: checkArgs,
4225
+ logFile: afterCheckLogFile,
4226
+ reject: false
4227
+ });
3661
4228
  let logContent = "";
4229
+ let afterCheckLogContent = "";
3662
4230
  try {
3663
4231
  logContent = readFileSync(logFile, "utf-8");
3664
4232
  } catch {
3665
4233
  }
4234
+ try {
4235
+ afterCheckLogContent = readFileSync(afterCheckLogFile, "utf-8");
4236
+ } catch {
4237
+ }
3666
4238
  const changeStats = parseSchemaChangeStats(logContent, null, repoRoot);
4239
+ const residualStats = parseSchemaChangeStats(afterCheckLogContent, null, repoRoot);
4240
+ const fullPostCheckOutput = `${postCheckResult.stdout || ""}
4241
+ ${postCheckResult.stderr || ""}`;
4242
+ const postCheckAnalysis = analyzePostCheckResult({
4243
+ exitCode: postCheckResult.exitCode ?? 1,
4244
+ output: fullPostCheckOutput,
4245
+ residualStats
4246
+ });
4247
+ if (postCheckAnalysis.commandFailed) {
4248
+ throw new Error(
4249
+ `Schema post-check failed (${postCheckResult.exitCode}): ${fullPostCheckOutput.substring(
4250
+ 0,
4251
+ 1e3
4252
+ )}`
4253
+ );
4254
+ }
3667
4255
  const schemaDrift = {
3668
4256
  beforeSql: logContent.substring(0, 5e3),
3669
4257
  // Truncate for comment
3670
- afterSql: null,
3671
- hasDrift: false,
4258
+ afterSql: postCheckAnalysis.hasDrift ? afterCheckLogContent.substring(0, 5e3) : "",
4259
+ checkExecuted: true,
4260
+ hasDrift: postCheckAnalysis.hasDrift,
3672
4261
  changeStats,
3673
4262
  gitDiff,
3674
4263
  logs: {
3675
4264
  beforeCheckLogPath: logFile,
3676
4265
  applyLogPath: logFile,
3677
- afterCheckLogPath: logFile
4266
+ afterCheckLogPath: afterCheckLogFile
3678
4267
  }
3679
4268
  };
3680
4269
  return { applied: true, schemaDrift };
@@ -3682,6 +4271,7 @@ var syncSchemaActor = fromPromise(
3682
4271
  const schemaDrift = {
3683
4272
  beforeSql: null,
3684
4273
  afterSql: null,
4274
+ checkExecuted: false,
3685
4275
  hasDrift: false,
3686
4276
  changeStats: null,
3687
4277
  gitDiff,
@@ -4345,6 +4935,213 @@ function safeReadFile(filePath) {
4345
4935
  return null;
4346
4936
  }
4347
4937
  }
4938
+ function buildSanitizeState() {
4939
+ return {
4940
+ inSingle: false,
4941
+ inDouble: false,
4942
+ inTemplate: false,
4943
+ inLineComment: false,
4944
+ inBlockComment: false
4945
+ };
4946
+ }
4947
+ function applySanitizeStep(state, stateUpdate) {
4948
+ return { ...state, ...stateUpdate };
4949
+ }
4950
+ function processLineCommentState(char) {
4951
+ return {
4952
+ output: char === "\n" ? "\n" : " ",
4953
+ nextIndex: 1,
4954
+ state: {
4955
+ inLineComment: char !== "\n"
4956
+ }
4957
+ };
4958
+ }
4959
+ function processBlockCommentState(char, next) {
4960
+ if (char === "*" && next === "/") {
4961
+ return {
4962
+ output: " ",
4963
+ nextIndex: 2,
4964
+ state: { inBlockComment: false }
4965
+ };
4966
+ }
4967
+ return {
4968
+ output: char === "\n" ? "\n" : " ",
4969
+ nextIndex: 1,
4970
+ state: {}
4971
+ };
4972
+ }
4973
+ function processSingleQuoteState(content, i, stripStrings) {
4974
+ const char = content[i] ?? "";
4975
+ if (char === "\\") {
4976
+ return {
4977
+ output: stripStrings ? " " : `${char}${content[i + 1] ?? ""}`,
4978
+ nextIndex: 2,
4979
+ state: {}
4980
+ };
4981
+ }
4982
+ if (char === "'") {
4983
+ return {
4984
+ output: char,
4985
+ nextIndex: 1,
4986
+ state: { inSingle: false }
4987
+ };
4988
+ }
4989
+ return {
4990
+ output: stripStrings ? " " : char,
4991
+ nextIndex: 1,
4992
+ state: {}
4993
+ };
4994
+ }
4995
+ function processDoubleQuoteState(content, i, stripStrings) {
4996
+ const char = content[i] ?? "";
4997
+ if (char === "\\") {
4998
+ return {
4999
+ output: stripStrings ? " " : `${char}${content[i + 1] ?? ""}`,
5000
+ nextIndex: 2,
5001
+ state: {}
5002
+ };
5003
+ }
5004
+ if (char === '"') {
5005
+ return {
5006
+ output: char,
5007
+ nextIndex: 1,
5008
+ state: { inDouble: false }
5009
+ };
5010
+ }
5011
+ return {
5012
+ output: stripStrings ? " " : char,
5013
+ nextIndex: 1,
5014
+ state: {}
5015
+ };
5016
+ }
5017
+ function processTemplateQuoteState(content, i, stripStrings) {
5018
+ const char = content[i] ?? "";
5019
+ if (char === "\\") {
5020
+ return {
5021
+ output: stripStrings ? " " : `${char}${content[i + 1] ?? ""}`,
5022
+ nextIndex: 2,
5023
+ state: {}
5024
+ };
5025
+ }
5026
+ if (char === "`") {
5027
+ return {
5028
+ output: "`",
5029
+ nextIndex: 1,
5030
+ state: { inTemplate: false }
5031
+ };
5032
+ }
5033
+ return {
5034
+ output: stripStrings ? char === "\n" ? "\n" : " " : char,
5035
+ nextIndex: 1,
5036
+ state: {}
5037
+ };
5038
+ }
5039
+ function processQuoteState(content, i, stripStrings, quote) {
5040
+ if (quote === "'") return processSingleQuoteState(content, i, stripStrings);
5041
+ if (quote === '"') return processDoubleQuoteState(content, i, stripStrings);
5042
+ return processTemplateQuoteState(content, i, stripStrings);
5043
+ }
5044
+ function processDefaultState(char, next) {
5045
+ if (char === "/" && next === "/") {
5046
+ return {
5047
+ output: " ",
5048
+ nextIndex: 2,
5049
+ state: { inLineComment: true }
5050
+ };
5051
+ }
5052
+ if (char === "/" && next === "*") {
5053
+ return {
5054
+ output: " ",
5055
+ nextIndex: 2,
5056
+ state: { inBlockComment: true }
5057
+ };
5058
+ }
5059
+ if (char === "'") {
5060
+ return {
5061
+ output: char,
5062
+ nextIndex: 1,
5063
+ state: { inSingle: true }
5064
+ };
5065
+ }
5066
+ if (char === '"') {
5067
+ return {
5068
+ output: char,
5069
+ nextIndex: 1,
5070
+ state: { inDouble: true }
5071
+ };
5072
+ }
5073
+ if (char === "`") {
5074
+ return {
5075
+ output: char,
5076
+ nextIndex: 1,
5077
+ state: { inTemplate: true }
5078
+ };
5079
+ }
5080
+ return { output: char, nextIndex: 1, state: {} };
5081
+ }
5082
+ function runSanitizeStep(step, state, result, currentIndex) {
5083
+ return {
5084
+ i: currentIndex + step.nextIndex,
5085
+ result: `${result}${step.output}`,
5086
+ state: applySanitizeStep(state, step.state)
5087
+ };
5088
+ }
5089
+ function sanitizeTsContent(content, options = {}) {
5090
+ const { stripStrings = false } = options;
5091
+ let result = "";
5092
+ let i = 0;
5093
+ let state = buildSanitizeState();
5094
+ while (i < content.length) {
5095
+ const char = content[i] ?? "";
5096
+ const next = content[i + 1] ?? "";
5097
+ if (state.inLineComment) {
5098
+ const step2 = processLineCommentState(char);
5099
+ const nextState2 = runSanitizeStep(step2, state, result, i);
5100
+ i = nextState2.i;
5101
+ result = nextState2.result;
5102
+ state = nextState2.state;
5103
+ continue;
5104
+ }
5105
+ if (state.inBlockComment) {
5106
+ const step2 = processBlockCommentState(char, next);
5107
+ const nextState2 = runSanitizeStep(step2, state, result, i);
5108
+ i = nextState2.i;
5109
+ result = nextState2.result;
5110
+ state = nextState2.state;
5111
+ continue;
5112
+ }
5113
+ if (state.inSingle) {
5114
+ const step2 = processQuoteState(content, i, stripStrings, "'");
5115
+ const nextState2 = runSanitizeStep(step2, state, result, i);
5116
+ i = nextState2.i;
5117
+ result = nextState2.result;
5118
+ state = nextState2.state;
5119
+ continue;
5120
+ }
5121
+ if (state.inDouble) {
5122
+ const step2 = processQuoteState(content, i, stripStrings, '"');
5123
+ const nextState2 = runSanitizeStep(step2, state, result, i);
5124
+ i = nextState2.i;
5125
+ result = nextState2.result;
5126
+ state = nextState2.state;
5127
+ continue;
5128
+ }
5129
+ if (state.inTemplate) {
5130
+ const step2 = processQuoteState(content, i, stripStrings, "`");
5131
+ const nextState2 = runSanitizeStep(step2, state, result, i);
5132
+ i = nextState2.i;
5133
+ result = nextState2.result;
5134
+ state = nextState2.state;
5135
+ continue;
5136
+ }
5137
+ const step = processDefaultState(char, next);
5138
+ const nextState = runSanitizeStep(step, state, result, i);
5139
+ i = nextState.i;
5140
+ result = nextState.result;
5141
+ state = nextState.state;
5142
+ }
5143
+ return result;
5144
+ }
4348
5145
  function readManifest(manifestPath) {
4349
5146
  try {
4350
5147
  if (!existsSync(manifestPath)) {
@@ -4389,11 +5186,12 @@ async function detectLayer1Content(repoRoot, manifest) {
4389
5186
  const filePath = join(repoRoot, file);
4390
5187
  const content = safeReadFile(filePath);
4391
5188
  if (!content) continue;
5189
+ const codeOnly = sanitizeTsContent(content, { stripStrings: true });
4392
5190
  const xstatePatterns = [
4393
5191
  /(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*createMachine\s*\(/,
4394
5192
  /(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*setup\s*\([^)]*\)\s*\.createMachine\s*\(/
4395
5193
  ];
4396
- const hasXStateMachine = xstatePatterns.some((p) => p.test(content));
5194
+ const hasXStateMachine = xstatePatterns.some((p) => p.test(codeOnly));
4397
5195
  if (hasXStateMachine) {
4398
5196
  machineCount++;
4399
5197
  }
@@ -4523,6 +5321,41 @@ async function detectLayer4Content(repoRoot, manifest) {
4523
5321
  }
4524
5322
  return createSkipStatus(base, "No user-facing pages found");
4525
5323
  }
5324
+ var detectStaticLayer = async (_repoRoot, _manifest) => ({
5325
+ layer: 0,
5326
+ hasContent: true,
5327
+ contentType: "machines",
5328
+ // Not really, but L0 always runs
5329
+ count: 1,
5330
+ source: "filesystem"
5331
+ });
5332
+ var layerDetectors = {
5333
+ 0: detectStaticLayer,
5334
+ 1: detectLayer1Content,
5335
+ 2: detectLayer2Content,
5336
+ 3: detectLayer3Content,
5337
+ 4: detectLayer4Content
5338
+ };
5339
+ function getUnknownLayerStatus(layer) {
5340
+ return {
5341
+ layer,
5342
+ hasContent: false,
5343
+ contentType: "machines",
5344
+ count: 0,
5345
+ source: "filesystem",
5346
+ skipReason: `Unknown layer ${layer}`
5347
+ };
5348
+ }
5349
+ async function detectLayerStatus(layer, repoRoot, manifest) {
5350
+ const detector = layerDetectors[layer];
5351
+ if (!detector) {
5352
+ return getUnknownLayerStatus(layer);
5353
+ }
5354
+ if (layer === 0) {
5355
+ return detectStaticLayer();
5356
+ }
5357
+ return detector(repoRoot, manifest);
5358
+ }
4526
5359
  async function detectLayerContent(repoRoot, selectedLayers, manifestPath) {
4527
5360
  const effectiveManifestPath = join(repoRoot, ".runa/manifests/manifest.json");
4528
5361
  const manifest = readManifest(effectiveManifestPath);
@@ -4530,40 +5363,7 @@ async function detectLayerContent(repoRoot, selectedLayers, manifestPath) {
4530
5363
  const skippedLayers = [];
4531
5364
  const runnableLayers = [];
4532
5365
  for (const layer of selectedLayers) {
4533
- let status;
4534
- switch (layer) {
4535
- case 0:
4536
- status = {
4537
- layer: 0,
4538
- hasContent: true,
4539
- contentType: "machines",
4540
- // Not really, but L0 always runs
4541
- count: 1,
4542
- source: "filesystem"
4543
- };
4544
- break;
4545
- case 1:
4546
- status = await detectLayer1Content(repoRoot, manifest);
4547
- break;
4548
- case 2:
4549
- status = await detectLayer2Content(repoRoot);
4550
- break;
4551
- case 3:
4552
- status = await detectLayer3Content(repoRoot, manifest);
4553
- break;
4554
- case 4:
4555
- status = await detectLayer4Content(repoRoot, manifest);
4556
- break;
4557
- default:
4558
- status = {
4559
- layer,
4560
- hasContent: false,
4561
- contentType: "machines",
4562
- count: 0,
4563
- source: "filesystem",
4564
- skipReason: `Unknown layer ${layer}`
4565
- };
4566
- }
5366
+ const status = await detectLayerStatus(layer, repoRoot, manifest);
4567
5367
  layers[layer] = status;
4568
5368
  if (status.hasContent) {
4569
5369
  runnableLayers.push(layer);
@@ -5248,38 +6048,70 @@ init_esm_shims();
5248
6048
  function emptyStats2() {
5249
6049
  return { tables: 0, functions: 0, policies: 0, indexes: 0, triggers: 0 };
5250
6050
  }
5251
- function formatLocalCell(stats) {
6051
+ function isStatsAvailable(stats) {
6052
+ return stats != null && stats.available !== false;
6053
+ }
6054
+ function isSemanticSnapshotAvailable(stats) {
6055
+ return stats != null && stats.available !== false && stats.canonicalSnapshot?.available === true;
6056
+ }
6057
+ function formatUnavailableCell(stats) {
6058
+ if (stats == null) return "n/a";
6059
+ return stats.error ? `\u26AA unavailable` : "n/a";
6060
+ }
6061
+ function formatReferenceCell(stats, isAvailable) {
6062
+ if (!isAvailable) return "\u26AA unavailable";
5252
6063
  return formatSchemaStatsCompact(stats, true);
5253
6064
  }
5254
- function formatCiCellWithDiff(stats, reference) {
5255
- if (hasDisplayedStatsDiff(stats, reference)) {
6065
+ function formatCiCellWithDiff(stats, reference, semanticSummary, isAvailable, referenceAvailable) {
6066
+ if (!isAvailable) return "\u26AA unavailable";
6067
+ if (!referenceAvailable) return formatSchemaStatsCompact(stats, true);
6068
+ if (hasDisplayedStatsDiff(stats, reference) || semanticSummary?.hasChanges) {
5256
6069
  return `\u26A0\uFE0F ${formatSchemaStatsWithDiff(stats, reference)}`;
5257
6070
  }
5258
6071
  return formatSchemaStatsCompact(stats, true);
5259
6072
  }
5260
- function formatProdCellWithDiff(stats, reference, schemaName, expectedDrift) {
5261
- if (!hasDisplayedStatsDiff(stats, reference)) {
6073
+ function formatProdCellWithDiff(stats, reference, schemaName, expectedDrift, semanticSummary, isAvailable, referenceAvailable) {
6074
+ if (!isAvailable) return "\u26AA unavailable";
6075
+ if (!referenceAvailable) return formatSchemaStatsCompact(stats, true);
6076
+ if (!hasDisplayedStatsDiff(stats, reference) && !semanticSummary?.hasChanges) {
5262
6077
  return formatSchemaStatsCompact(stats, true);
5263
6078
  }
5264
6079
  const fieldDiffs = getFieldDiffs(stats, reference);
5265
- const allExpected = expectedDrift.length > 0 && isAllDriftExpected(schemaName, fieldDiffs, expectedDrift);
6080
+ const allExpected = !semanticSummary?.hasChanges && expectedDrift.length > 0 && isAllDriftExpected(schemaName, fieldDiffs, expectedDrift);
5266
6081
  const marker = allExpected ? "\u{1F4CB}" : "\u{1F4DD}";
5267
6082
  return `${marker} ${formatSchemaStatsWithDiff(stats, reference)}`;
5268
6083
  }
5269
- function formatCiTotalCellWithDiff(stats, reference) {
6084
+ function formatCiTotalCellWithDiff(stats, reference, hasSemanticChanges, isAvailable, referenceAvailable) {
6085
+ if (!isAvailable) return "**\u26AA unavailable**";
6086
+ if (!referenceAvailable) return `**${formatSchemaStatsCompact(stats, true)}**`;
5270
6087
  const formatted = formatSchemaStatsCompact(stats, true);
5271
- if (hasDisplayedStatsDiff(stats, reference)) {
6088
+ if (hasDisplayedStatsDiff(stats, reference) || hasSemanticChanges) {
5272
6089
  return `\u26A0\uFE0F **${formatted}**`;
5273
6090
  }
5274
6091
  return `**${formatted}**`;
5275
6092
  }
5276
- function formatProdTotalCellWithDiff(stats, reference) {
6093
+ function formatProdTotalCellWithDiff(stats, reference, hasSemanticChanges, isAvailable, referenceAvailable) {
6094
+ if (!isAvailable) return "**\u26AA unavailable**";
6095
+ if (!referenceAvailable) return `**${formatSchemaStatsCompact(stats, true)}**`;
5277
6096
  const formatted = formatSchemaStatsCompact(stats, true);
5278
- if (hasDisplayedStatsDiff(stats, reference)) {
6097
+ if (hasDisplayedStatsDiff(stats, reference) || hasSemanticChanges) {
5279
6098
  return `\u{1F4DD} **${formatted}**`;
5280
6099
  }
5281
6100
  return `**${formatted}**`;
5282
6101
  }
6102
+ function buildCanonicalDiffs(schemaStats) {
6103
+ const referenceSnapshot = schemaStats.local.canonicalSnapshot;
6104
+ const ciSnapshot = schemaStats.ci.canonicalSnapshot;
6105
+ const productionSnapshot = schemaStats.production?.canonicalSnapshot;
6106
+ const ciDiff = isSemanticSnapshotAvailable(schemaStats.local) && isSemanticSnapshotAvailable(schemaStats.ci) ? compareCanonicalSnapshots(referenceSnapshot, ciSnapshot) : null;
6107
+ const productionDiff = isSemanticSnapshotAvailable(schemaStats.local) && isSemanticSnapshotAvailable(schemaStats.production) ? compareCanonicalSnapshots(referenceSnapshot, productionSnapshot) : null;
6108
+ return {
6109
+ ci: ciDiff,
6110
+ production: productionDiff,
6111
+ ciBySchema: ciDiff ? summarizeCanonicalDiffBySchema(ciDiff) : {},
6112
+ productionBySchema: productionDiff ? summarizeCanonicalDiffBySchema(productionDiff) : {}
6113
+ };
6114
+ }
5283
6115
  function getSortedActiveSchemas(schemaStats) {
5284
6116
  const allSchemas = /* @__PURE__ */ new Set([
5285
6117
  ...getActiveSchemas(schemaStats.local),
@@ -5292,37 +6124,67 @@ function getSortedActiveSchemas(schemaStats) {
5292
6124
  return a.localeCompare(b);
5293
6125
  });
5294
6126
  }
5295
- function generateSchemaTableRows(schemaStats, sortedSchemas, hasProduction, expectedDrift) {
6127
+ function generateSchemaTableRows(schemaStats, sortedSchemas, hasProduction, expectedDrift, ciDiffBySchema, productionDiffBySchema) {
5296
6128
  const lines = [];
5297
6129
  for (const schemaName of sortedSchemas) {
5298
6130
  const localStats = schemaStats.local.schemas[schemaName] ?? emptyStats2();
5299
6131
  const ciStats = schemaStats.ci.schemas[schemaName] ?? emptyStats2();
5300
6132
  const prodStats = schemaStats.production?.schemas[schemaName] ?? null;
5301
- const localCell = formatLocalCell(localStats);
5302
- const ciCell = formatCiCellWithDiff(ciStats, localStats);
5303
- const prodCell = prodStats !== null ? formatProdCellWithDiff(prodStats, localStats, schemaName, expectedDrift) : null;
6133
+ const localCell = formatReferenceCell(localStats, isStatsAvailable(schemaStats.local));
6134
+ const ciCell = formatCiCellWithDiff(
6135
+ ciStats,
6136
+ localStats,
6137
+ ciDiffBySchema[schemaName],
6138
+ isStatsAvailable(schemaStats.ci),
6139
+ isStatsAvailable(schemaStats.local)
6140
+ );
6141
+ const prodCell = prodStats !== null ? formatProdCellWithDiff(
6142
+ prodStats,
6143
+ localStats,
6144
+ schemaName,
6145
+ expectedDrift,
6146
+ productionDiffBySchema[schemaName],
6147
+ isStatsAvailable(schemaStats.production),
6148
+ isStatsAvailable(schemaStats.local)
6149
+ ) : formatUnavailableCell(schemaStats.production);
5304
6150
  lines.push(
5305
6151
  hasProduction ? `| ${schemaName} | ${localCell} | ${ciCell} | ${prodCell ?? "n/a"} |` : `| ${schemaName} | ${localCell} | ${ciCell} |`
5306
6152
  );
5307
6153
  }
5308
6154
  return lines;
5309
6155
  }
5310
- function generateSchemaMatrixSummary(localTotal, ciTotal, prodTotal) {
5311
- const ciSynced = !hasDisplayedStatsDiff(localTotal, ciTotal);
5312
- const prodSynced = prodTotal === null || !hasDisplayedStatsDiff(localTotal, prodTotal);
5313
- if (ciSynced && prodSynced) {
5314
- return `\u{1F4CA} **Local:** ${formatTotalStatsCompact(localTotal)}`;
6156
+ function generateSchemaMatrixSummary(localTotal, ciTotal, prodTotal, hasCiSemanticDiff, hasProdSemanticDiff, localAvailable, ciAvailable, prodAvailable) {
6157
+ if (!localAvailable) {
6158
+ const parts2 = ["\u{1F4CA} **Reference:** unavailable"];
6159
+ if (ciAvailable) parts2.push(`| **CI:** ${formatTotalStatsCompact(ciTotal)}`);
6160
+ else parts2.push("| **CI:** unavailable");
6161
+ if (prodTotal !== null) {
6162
+ parts2.push(
6163
+ prodAvailable ? `| **Prod:** ${formatTotalStatsCompact(prodTotal)}` : "| **Prod:** unavailable"
6164
+ );
6165
+ }
6166
+ return parts2.join(" ");
6167
+ }
6168
+ const ciSynced = ciAvailable && !hasDisplayedStatsDiff(localTotal, ciTotal) && !hasCiSemanticDiff;
6169
+ const prodSynced = prodTotal === null || prodAvailable && !hasDisplayedStatsDiff(localTotal, prodTotal) && !hasProdSemanticDiff;
6170
+ if (ciSynced && prodSynced && ciAvailable && (prodTotal === null || prodAvailable)) {
6171
+ return `\u{1F4CA} **Reference:** ${formatTotalStatsCompact(localTotal)}`;
5315
6172
  }
5316
- const parts = [`\u{1F4CA} **Local:** ${formatTotalStatsCompact(localTotal)}`];
5317
- if (!ciSynced) parts.push(`| **CI:** ${formatTotalStatsCompact(ciTotal)}`);
6173
+ const parts = [`\u{1F4CA} **Reference:** ${formatTotalStatsCompact(localTotal)}`];
6174
+ if (!ciAvailable) parts.push("| **CI:** unavailable");
6175
+ else if (!ciSynced) parts.push(`| **CI:** ${formatTotalStatsCompact(ciTotal)}`);
5318
6176
  if (prodTotal !== null && !prodSynced) {
5319
- parts.push(`| **Prod:** \u{1F4DD} ${formatTotalStatsCompact(prodTotal)}`);
6177
+ parts.push(
6178
+ prodAvailable ? `| **Prod:** \u{1F4DD} ${formatTotalStatsCompact(prodTotal)}` : "| **Prod:** unavailable"
6179
+ );
5320
6180
  }
5321
6181
  return parts.join(" ");
5322
6182
  }
5323
6183
  function generateIndexDiffSection(schemaStats) {
5324
- if (!schemaStats.production) return [];
5325
- const diff = compareIndexLists(schemaStats.local, schemaStats.production);
6184
+ if (!isStatsAvailable(schemaStats.local) || !isStatsAvailable(schemaStats.production)) return [];
6185
+ const productionStats = schemaStats.production;
6186
+ if (!productionStats) return [];
6187
+ const diff = compareIndexLists(schemaStats.local, productionStats);
5326
6188
  if (!hasIndexDiff(diff)) return [];
5327
6189
  const lines = [];
5328
6190
  if (diff.missing.length > 0) {
@@ -5342,8 +6204,85 @@ function generateIndexDiffSection(schemaStats) {
5342
6204
  }
5343
6205
  return lines;
5344
6206
  }
6207
+ function appendAvailabilityNote(lines, label, error, kind) {
6208
+ const scope = kind === "stats" ? "schema stats" : "semantic diff";
6209
+ lines.push(`> \u26AA **${label} ${scope} unavailable:** ${error ?? "unknown error"}`);
6210
+ }
6211
+ function generateAvailabilityNotes(schemaStats) {
6212
+ const lines = [];
6213
+ const appendNotesForEnv = (label, stats) => {
6214
+ if (stats == null) return;
6215
+ if (!isStatsAvailable(stats)) {
6216
+ appendAvailabilityNote(lines, label, stats.error, "stats");
6217
+ return;
6218
+ }
6219
+ if (stats.canonicalSnapshot?.available === false) {
6220
+ appendAvailabilityNote(lines, label, stats.canonicalSnapshot.error, "semantic");
6221
+ }
6222
+ };
6223
+ appendNotesForEnv("Reference", schemaStats.local);
6224
+ appendNotesForEnv("CI", schemaStats.ci);
6225
+ appendNotesForEnv("Prod", schemaStats.production);
6226
+ if (lines.length === 0) return [];
6227
+ return ["", ...lines, ">"];
6228
+ }
6229
+ function describeObject(object) {
6230
+ switch (object.kind) {
6231
+ case "index":
6232
+ return `index \`${object.label}\``;
6233
+ case "policy":
6234
+ return `policy \`${object.label}\``;
6235
+ case "trigger":
6236
+ return `trigger \`${object.label}\``;
6237
+ case "function":
6238
+ return `function \`${object.label}\``;
6239
+ case "table":
6240
+ return `table \`${object.label}\``;
6241
+ }
6242
+ }
6243
+ function appendObjectLines(lines, prefix, objects, label) {
6244
+ if (objects.length === 0) return;
6245
+ lines.push(`> ${prefix} **${label}:**`);
6246
+ for (const object of objects.slice(0, 6)) {
6247
+ lines.push(`> - ${describeObject(object)}`);
6248
+ }
6249
+ if (objects.length > 6) {
6250
+ lines.push(`> - ... \u4ED6${objects.length - 6}\u4EF6`);
6251
+ }
6252
+ lines.push(">");
6253
+ }
6254
+ function appendChangedObjectLines(lines, prefix, pairs, label) {
6255
+ if (pairs.length === 0) return;
6256
+ lines.push(`> ${prefix} **${label}:**`);
6257
+ for (const pair of pairs.slice(0, 6)) {
6258
+ lines.push(`> - ${describeObject(pair.reference)}`);
6259
+ }
6260
+ if (pairs.length > 6) {
6261
+ lines.push(`> - ... \u4ED6${pairs.length - 6}\u4EF6`);
6262
+ }
6263
+ lines.push(">");
6264
+ }
6265
+ function generateSemanticDiffSection(title, prefix, diff) {
6266
+ if (!diff || !hasCanonicalChanges(diff)) return [];
6267
+ const lines = ["", `> ${prefix} **${title} semantic diff:**`, ">"];
6268
+ appendObjectLines(lines, prefix, diff.missing, "missing objects");
6269
+ appendObjectLines(lines, prefix, diff.extra, "extra objects");
6270
+ appendChangedObjectLines(lines, prefix, diff.changed, "changed objects");
6271
+ if (diff.renamed.length > 0) {
6272
+ lines.push(`> ${prefix} **renamed indexes:**`);
6273
+ for (const pair of diff.renamed.slice(0, 6)) {
6274
+ lines.push(`> - \`${pair.reference.label}\` -> \`${pair.target.label}\``);
6275
+ }
6276
+ if (diff.renamed.length > 6) {
6277
+ lines.push(`> - ... \u4ED6${diff.renamed.length - 6}\u4EF6`);
6278
+ }
6279
+ lines.push(">");
6280
+ }
6281
+ return lines;
6282
+ }
5345
6283
  function generateExpectedDriftFootnotes(schemaStats, sortedSchemas, expectedDrift) {
5346
6284
  if (expectedDrift.length === 0 || !schemaStats.production) return [];
6285
+ if (!isStatsAvailable(schemaStats.local) || !isStatsAvailable(schemaStats.production)) return [];
5347
6286
  const allReasons = /* @__PURE__ */ new Set();
5348
6287
  for (const schemaName of sortedSchemas) {
5349
6288
  const localStats = schemaStats.local.schemas[schemaName] ?? emptyStats2();
@@ -5370,33 +6309,69 @@ function formatSchemaMatrix(schemaStats, gitDiff, expectedDrift = []) {
5370
6309
  const hasGitChanges = gitDiff && gitDiff.filesChanged.length > 0;
5371
6310
  const header = hasGitChanges ? `### \u30B9\u30AD\u30FC\u30DE\u72B6\u6CC1 (+${gitDiff.linesAdded}/-${gitDiff.linesDeleted} PR\u5185)` : "### \u30B9\u30AD\u30FC\u30DE\u72B6\u6CC1";
5372
6311
  const sortedSchemas = getSortedActiveSchemas(schemaStats);
6312
+ const availabilityNotes = generateAvailabilityNotes(schemaStats);
5373
6313
  if (sortedSchemas.length === 0) {
5374
- return [header, "", "_\u30B9\u30AD\u30FC\u30DE\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u304C\u3042\u308A\u307E\u305B\u3093_", ""];
6314
+ return [header, "", ...availabilityNotes, "_\u30B9\u30AD\u30FC\u30DE\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u304C\u3042\u308A\u307E\u305B\u3093_", ""];
5375
6315
  }
5376
6316
  const hasProduction = schemaStats.production !== null;
5377
- const tableHeader = hasProduction ? ["| Schema | \u{1F4C4} Local | \u{1F52C} CI | \u{1F3ED} Prod |", "|--------|:--------:|:-----:|:-------:|"] : ["| Schema | \u{1F4C4} Local | \u{1F52C} CI |", "|--------|:--------:|:-----:|"];
5378
- const rows = generateSchemaTableRows(schemaStats, sortedSchemas, hasProduction, expectedDrift);
6317
+ const canonicalDiffs = buildCanonicalDiffs(schemaStats);
6318
+ const tableHeader = hasProduction ? ["| Schema | \u{1F4C4} Reference | \u{1F52C} CI | \u{1F3ED} Prod |", "|--------|:------------:|:-----:|:-------:|"] : ["| Schema | \u{1F4C4} Reference | \u{1F52C} CI |", "|--------|:------------:|:-----:|"];
6319
+ const rows = generateSchemaTableRows(
6320
+ schemaStats,
6321
+ sortedSchemas,
6322
+ hasProduction,
6323
+ expectedDrift,
6324
+ canonicalDiffs.ciBySchema,
6325
+ canonicalDiffs.productionBySchema
6326
+ );
5379
6327
  const { local, ci, production } = schemaStats;
5380
- const localTotalCell = `**${formatSchemaStatsCompact(local.total, true)}**`;
5381
- const ciTotalCell = formatCiTotalCellWithDiff(ci.total, local.total);
5382
- const prodTotalCell = production ? formatProdTotalCellWithDiff(production.total, local.total) : null;
6328
+ const localTotalCell = isStatsAvailable(local) ? `**${formatSchemaStatsCompact(local.total, true)}**` : "**\u26AA unavailable**";
6329
+ const ciTotalCell = formatCiTotalCellWithDiff(
6330
+ ci.total,
6331
+ local.total,
6332
+ canonicalDiffs.ci ? hasCanonicalChanges(canonicalDiffs.ci) : false,
6333
+ isStatsAvailable(ci),
6334
+ isStatsAvailable(local)
6335
+ );
6336
+ const prodTotalCell = production ? formatProdTotalCellWithDiff(
6337
+ production.total,
6338
+ local.total,
6339
+ canonicalDiffs.production ? hasCanonicalChanges(canonicalDiffs.production) : false,
6340
+ isStatsAvailable(production),
6341
+ isStatsAvailable(local)
6342
+ ) : null;
5383
6343
  const totalRow = hasProduction ? `| **Total** | ${localTotalCell} | ${ciTotalCell} | ${prodTotalCell ?? "n/a"} |` : `| **Total** | ${localTotalCell} | ${ciTotalCell} |`;
5384
- const summary = generateSchemaMatrixSummary(local.total, ci.total, production?.total ?? null);
6344
+ const summary = generateSchemaMatrixSummary(
6345
+ local.total,
6346
+ ci.total,
6347
+ production?.total ?? null,
6348
+ canonicalDiffs.ci ? hasCanonicalChanges(canonicalDiffs.ci) : false,
6349
+ canonicalDiffs.production ? hasCanonicalChanges(canonicalDiffs.production) : false,
6350
+ isStatsAvailable(local),
6351
+ isStatsAvailable(ci),
6352
+ isStatsAvailable(production)
6353
+ );
5385
6354
  const indexDiffSection = generateIndexDiffSection(schemaStats);
5386
6355
  const expectedDriftNotes = generateExpectedDriftFootnotes(
5387
6356
  schemaStats,
5388
6357
  sortedSchemas,
5389
6358
  expectedDrift
5390
6359
  );
6360
+ const semanticDiffSection = [
6361
+ ...generateSemanticDiffSection("CI", "\u26A0\uFE0F", canonicalDiffs.ci),
6362
+ ...generateSemanticDiffSection("Prod", "\u{1F4DD}", canonicalDiffs.production)
6363
+ ];
5391
6364
  return [
5392
6365
  header,
5393
6366
  "",
6367
+ ...availabilityNotes,
5394
6368
  ...tableHeader,
5395
6369
  ...rows,
5396
6370
  totalRow,
5397
6371
  "",
5398
6372
  summary,
5399
6373
  ...indexDiffSection,
6374
+ ...semanticDiffSection,
5400
6375
  ...expectedDriftNotes,
5401
6376
  "",
5402
6377
  "<sub>T=\u30C6\u30FC\u30D6\u30EB F=\u95A2\u6570 P=\u30DD\u30EA\u30B7\u30FC I=\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9 Tr=\u30C8\u30EA\u30AC\u30FC</sub>",
@@ -5406,8 +6381,8 @@ function formatSchemaMatrix(schemaStats, gitDiff, expectedDrift = []) {
5406
6381
 
5407
6382
  // src/commands/ci/machine/formatters/sections/final-comment.ts
5408
6383
  function formatDriftStatus(drift) {
5409
- if (drift == null) {
5410
- return "_n/a_";
6384
+ if (drift == null || !drift.checkExecuted) {
6385
+ return "_\u672A\u691C\u8A3C_";
5411
6386
  }
5412
6387
  return drift.hasDrift ? "**\u274C \u30C9\u30EA\u30D5\u30C8\u691C\u51FA (schemas \u2194 CI DB)**" : "**\u2705 \u30C9\u30EA\u30D5\u30C8\u306A\u3057 (schemas \u2194 CI DB)**";
5413
6388
  }
@@ -5534,6 +6509,8 @@ function classifyPreviewError(error) {
5534
6509
  }
5535
6510
  function generateIndexDiffWarning(schemaStats) {
5536
6511
  if (!schemaStats?.local || !schemaStats?.production) return [];
6512
+ if (schemaStats.local.available === false || schemaStats.production.available === false)
6513
+ return [];
5537
6514
  const indexDiff = compareIndexLists(schemaStats.local, schemaStats.production);
5538
6515
  if (!hasIndexDiff(indexDiff)) return [];
5539
6516
  const diffParts = [];
@@ -6410,7 +7387,7 @@ function createBuildAndPlaywrightInput(context) {
6410
7387
  enablePublicE2EFlag: true
6411
7388
  }),
6412
7389
  isCI: context.input.isCI ?? false,
6413
- skipPlaywright: context.selectedLayers.includes(4) ? false : true
7390
+ skipPlaywright: !context.selectedLayers.includes(4)
6414
7391
  };
6415
7392
  }
6416
7393
  function createAppStartInput(context) {
@@ -6845,11 +7822,12 @@ var ciMachine = setup({
6845
7822
  invoke: {
6846
7823
  src: "collectSchemaStats",
6847
7824
  input: ({ context }) => ({
6848
- // In CI mode, local and CI both use the Docker Supabase
6849
- localDbUrl: context.supabase?.databaseUrlRaw ?? null,
7825
+ repoRoot: context.repoRoot ?? process.cwd(),
7826
+ referenceDbUrl: context.supabase?.databaseUrlRaw ?? null,
6850
7827
  ciDbUrl: context.supabase?.databaseUrlRaw ?? null,
6851
7828
  // Query production only in ci-pr modes
6852
- queryProduction: context.mode !== "ci-local"
7829
+ queryProduction: context.mode !== "ci-local",
7830
+ tmpDir: context.tmpDir ?? process.cwd()
6853
7831
  }),
6854
7832
  onDone: {
6855
7833
  target: "decidePath",
@@ -7704,16 +8682,13 @@ var stateLogHandlers = {
7704
8682
  logger.info(`Running: ${ctx.selectedLayers.map((l) => `test layer${l}`).join(", ")}`);
7705
8683
  }
7706
8684
  };
7707
- stateLogHandlers["setup.resolving"] = stateLogHandlers.setup;
7708
- stateLogHandlers["setup.local"] = stateLogHandlers.setup;
7709
- function handleStateChange(snapshot, prevState, logger) {
7710
- const state = getStateName(snapshot);
7711
- if (state === prevState) return;
7712
- const handler = stateLogHandlers[state];
7713
- if (handler) {
7714
- handler(snapshot.context, logger);
7715
- }
7716
- }
8685
+ var handleStateChange = createMachineStateChangeLogger({
8686
+ getState: getStateName,
8687
+ getContext: (snapshot) => snapshot.context,
8688
+ handlers: stateLogHandlers,
8689
+ useParentState: true,
8690
+ handlerArgumentOrder: "context-first"
8691
+ });
7717
8692
  function printSummary(logger, output) {
7718
8693
  endCurrentGroup();
7719
8694
  logSection2("Summary");
@@ -8052,6 +9027,7 @@ function logPlan2(steps) {
8052
9027
  console.log("");
8053
9028
  }
8054
9029
  }
9030
+ var CI_PR_UNKNOWN_STATE_LOG_SKIP = /* @__PURE__ */ new Set(["decidePath", "done", "failed"]);
8055
9031
  var stateLogHandlers2 = {
8056
9032
  // ─── Setup Phase ───────────────────────────────────────────
8057
9033
  setup: (_ctx, _logger) => {
@@ -8186,18 +9162,18 @@ var stateLogHandlers2 = {
8186
9162
  logSection3("Finalize: Post GitHub Comment");
8187
9163
  }
8188
9164
  };
8189
- stateLogHandlers2["setup.resolving"] = stateLogHandlers2.setup;
8190
- stateLogHandlers2["finalize.writeSummary"] = stateLogHandlers2.finalize;
8191
- function handleStateChange2(snapshot, prevState, logger) {
8192
- const state = getStateName(snapshot);
8193
- if (state === prevState) return;
8194
- const handler = stateLogHandlers2[state];
8195
- if (handler) {
8196
- handler(snapshot.context, logger);
8197
- } else if (cliOutputFormat !== "json" && state !== "decidePath" && state !== "done" && state !== "failed") {
8198
- logger.info(`State: ${state}`);
9165
+ var handleStateChange2 = createMachineStateChangeLogger({
9166
+ getState: getStateName,
9167
+ getContext: (snapshot) => snapshot.context,
9168
+ handlers: stateLogHandlers2,
9169
+ useParentState: true,
9170
+ handlerArgumentOrder: "context-first",
9171
+ onUnknownState: (state, logger) => {
9172
+ if (cliOutputFormat !== "json" && !CI_PR_UNKNOWN_STATE_LOG_SKIP.has(state)) {
9173
+ logger.info(`State: ${state}`);
9174
+ }
8199
9175
  }
8200
- }
9176
+ });
8201
9177
  function printSummary2(logger, output) {
8202
9178
  logSection3("Summary");
8203
9179
  logger.info(`Mode: ${output.mode}`);