{
  "schemaVersion": "1.0",
  "item": {
    "slug": "ecto-migrator",
    "name": "Ecto Migrator",
    "source": "tencent",
    "type": "skill",
    "category": "AI 智能",
    "sourceUrl": "https://clawhub.ai/gchapim/ecto-migrator",
    "canonicalUrl": "https://clawhub.ai/gchapim/ecto-migrator",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/ecto-migrator",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=ecto-migrator",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "references/index-patterns.md",
      "references/column-types.md",
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "slug": "ecto-migrator",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-05-01T07:39:41.242Z",
      "expiresAt": "2026-05-08T07:39:41.242Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=ecto-migrator",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=ecto-migrator",
        "contentDisposition": "attachment; filename=\"ecto-migrator-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null,
        "slug": "ecto-migrator"
      },
      "scope": "item",
      "summary": "Item download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this item.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/ecto-migrator"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/ecto-migrator",
    "agentPageUrl": "https://openagent3.xyz/skills/ecto-migrator/agent",
    "manifestUrl": "https://openagent3.xyz/skills/ecto-migrator/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/ecto-migrator/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "From Natural Language",
        "body": "Parse the user's description and generate a migration file. Common patterns:\n\nUser SaysMigration Action\"Create users table with email and name\"create table(:users) with columns\"Add phone to users\"alter table(:users), add :phone\"Make email unique on users\"create unique_index(:users, [:email])\"Add tenant_id to all tables\"Multiple alter table with index\"Rename status to state on orders\"rename table(:orders), :status, to: :state\"Remove the legacy_id column from users\"alter table(:users), remove :legacy_id\"Add a check constraint on orders amount > 0\"create constraint(:orders, ...)"
      },
      {
        "title": "File Naming",
        "body": "mix ecto.gen.migration <name>\n# Generates: priv/repo/migrations/YYYYMMDDHHMMSS_<name>.exs\n\nName conventions: create_<table>, add_<column>_to_<table>, create_<table>_<column>_index, alter_<table>_add_<columns>."
      },
      {
        "title": "Migration Template",
        "body": "defmodule MyApp.Repo.Migrations.CreateUsers do\n  use Ecto.Migration\n\n  def change do\n    create table(:users, primary_key: false) do\n      add :id, :binary_id, primary_key: true\n      add :email, :string, null: false\n      add :name, :string, null: false\n      add :role, :string, null: false, default: \"member\"\n      add :metadata, :map, default: %{}\n      add :tenant_id, :binary_id, null: false\n\n      add :team_id, references(:teams, type: :binary_id, on_delete: :delete_all)\n\n      timestamps(type: :utc_datetime_usec)\n    end\n\n    create unique_index(:users, [:tenant_id, :email])\n    create index(:users, [:tenant_id])\n    create index(:users, [:team_id])\n  end\nend"
      },
      {
        "title": "Column Types",
        "body": "See references/column-types.md for complete type mapping and guidance.\n\nKey decisions:\n\nIDs: Use :binary_id (UUID) — set primary_key: false on table, add :id manually.\nMoney: Use :integer (cents) or :decimal — never :float.\nTimestamps: Always timestamps(type: :utc_datetime_usec).\nEnums: Use :string with app-level Ecto.Enum — avoid Postgres enums (hard to migrate).\nJSON: Use :map (maps to jsonb).\nArrays: Use {:array, :string} etc."
      },
      {
        "title": "Index Strategies",
        "body": "See references/index-patterns.md for detailed index guidance."
      },
      {
        "title": "When to Add Indexes",
        "body": "Always index:\n\nForeign keys (_id columns)\ntenant_id (first column in composite indexes)\nColumns used in WHERE clauses\nColumns used in ORDER BY\nUnique constraints"
      },
      {
        "title": "Index Types",
        "body": "# Standard B-tree\ncreate index(:users, [:tenant_id])\n\n# Unique\ncreate unique_index(:users, [:tenant_id, :email])\n\n# Partial (conditional)\ncreate index(:orders, [:status], where: \"status != 'completed'\", name: :orders_active_status_idx)\n\n# GIN for JSONB\ncreate index(:events, [:metadata], using: :gin)\n\n# GIN for array columns\ncreate index(:posts, [:tags], using: :gin)\n\n# Composite\ncreate index(:orders, [:tenant_id, :status, :inserted_at])\n\n# Concurrent (no table lock — use in separate migration)\n@disable_ddl_transaction true\n@disable_migration_lock true\n\ndef change do\n  create index(:users, [:email], concurrently: true)\nend"
      },
      {
        "title": "Constraints",
        "body": "# Check constraint\ncreate constraint(:orders, :amount_must_be_positive, check: \"amount > 0\")\n\n# Exclusion constraint (requires btree_gist extension)\nexecute \"CREATE EXTENSION IF NOT EXISTS btree_gist\", \"\"\ncreate constraint(:reservations, :no_overlapping_bookings,\n  exclude: ~s|gist (room_id WITH =, tstzrange(starts_at, ends_at) WITH &&)|\n)\n\n# Unique constraint (same as unique_index for most purposes)\ncreate unique_index(:accounts, [:slug])"
      },
      {
        "title": "References (Foreign Keys)",
        "body": "add :user_id, references(:users, type: :binary_id, on_delete: :delete_all), null: false\nadd :team_id, references(:teams, type: :binary_id, on_delete: :nilify_all)\nadd :parent_id, references(:categories, type: :binary_id, on_delete: :nothing)\n\non_deleteUse When:delete_allChild can't exist without parent (memberships, line items):nilify_allChild should survive parent deletion (optional association):nothingHandle in application code (default):restrictPrevent parent deletion if children exist"
      },
      {
        "title": "Every Table Gets tenant_id",
        "body": "def change do\n  create table(:items, primary_key: false) do\n    add :id, :binary_id, primary_key: true\n    add :name, :string, null: false\n    add :tenant_id, :binary_id, null: false\n    timestamps(type: :utc_datetime_usec)\n  end\n\n  # Always composite index with tenant_id first\n  create index(:items, [:tenant_id])\n  create unique_index(:items, [:tenant_id, :name])\nend"
      },
      {
        "title": "Adding tenant_id to Existing Tables",
        "body": "def change do\n  alter table(:items) do\n    add :tenant_id, :binary_id\n  end\n\n  # Backfill in a separate data migration, then:\n  # alter table(:items) do\n  #   modify :tenant_id, :binary_id, null: false\n  # end\nend"
      },
      {
        "title": "Data Migrations",
        "body": "Rule: Never mix schema changes and data changes in the same migration."
      },
      {
        "title": "Safe Data Migration Pattern",
        "body": "defmodule MyApp.Repo.Migrations.BackfillUserRoles do\n  use Ecto.Migration\n\n  # Don't use schema modules — they may change after this migration runs\n  def up do\n    execute \"\"\"\n    UPDATE users SET role = 'member' WHERE role IS NULL\n    \"\"\"\n  end\n\n  def down do\n    # Data migrations may not be reversible\n    :ok\n  end\nend"
      },
      {
        "title": "Batched Data Migration (large tables)",
        "body": "def up do\n  execute \"\"\"\n  UPDATE users SET role = 'member'\n  WHERE id IN (\n    SELECT id FROM users WHERE role IS NULL LIMIT 10000\n  )\n  \"\"\"\n\n  # For very large tables, use a Task or Oban job instead\nend"
      },
      {
        "title": "Reversible (use change)",
        "body": "These are auto-reversible:\n\ncreate table ↔ drop table\nadd column ↔ remove column\ncreate index ↔ drop index\nrename ↔ rename"
      },
      {
        "title": "Irreversible (use up/down)",
        "body": "Must define both directions:\n\nmodify column type — Ecto can't infer the old type\nexecute raw SQL\nData backfills\nDropping columns with data\n\ndef up do\n  alter table(:users) do\n    modify :email, :citext, from: :string  # from: helps reversibility\n  end\nend\n\ndef down do\n  alter table(:users) do\n    modify :email, :string, from: :citext\n  end\nend"
      },
      {
        "title": "Using modify with from:",
        "body": "Phoenix 1.7+ supports from: for reversible modify:\n\ndef change do\n  alter table(:users) do\n    modify :email, :citext, null: false, from: {:string, null: true}\n  end\nend"
      },
      {
        "title": "PostgreSQL Extensions",
        "body": "def change do\n  execute \"CREATE EXTENSION IF NOT EXISTS citext\", \"DROP EXTENSION IF EXISTS citext\"\n  execute \"CREATE EXTENSION IF NOT EXISTS pgcrypto\", \"DROP EXTENSION IF EXISTS pgcrypto\"\n  execute \"CREATE EXTENSION IF NOT EXISTS pg_trgm\", \"DROP EXTENSION IF EXISTS pg_trgm\"\nend"
      },
      {
        "title": "Enum Types (PostgreSQL native — use sparingly)",
        "body": "Prefer Ecto.Enum with :string columns. If you must use Postgres enums:\n\ndef up do\n  execute \"CREATE TYPE order_status AS ENUM ('pending', 'confirmed', 'shipped', 'delivered')\"\n\n  alter table(:orders) do\n    add :status, :order_status, null: false, default: \"pending\"\n  end\nend\n\ndef down do\n  alter table(:orders) do\n    remove :status\n  end\n\n  execute \"DROP TYPE order_status\"\nend\n\nWarning: Adding values to Postgres enums requires ALTER TYPE ... ADD VALUE which cannot run inside a transaction. Prefer :string + Ecto.Enum."
      },
      {
        "title": "Checklist",
        "body": "Primary key: primary_key: false + add :id, :binary_id, primary_key: true\n null: false on required columns\n timestamps(type: :utc_datetime_usec)\n Foreign keys with appropriate on_delete\n Index on every foreign key column\n tenant_id indexed (composite with lookup fields)\n Unique constraints where needed\n Concurrent indexes in separate migration with @disable_ddl_transaction true\n Data migrations in separate files from schema migrations"
      }
    ],
    "body": "Ecto Migrator\nGenerating Migrations\nFrom Natural Language\n\nParse the user's description and generate a migration file. Common patterns:\n\nUser Says\tMigration Action\n\"Create users table with email and name\"\tcreate table(:users) with columns\n\"Add phone to users\"\talter table(:users), add :phone\n\"Make email unique on users\"\tcreate unique_index(:users, [:email])\n\"Add tenant_id to all tables\"\tMultiple alter table with index\n\"Rename status to state on orders\"\trename table(:orders), :status, to: :state\n\"Remove the legacy_id column from users\"\talter table(:users), remove :legacy_id\n\"Add a check constraint on orders amount > 0\"\tcreate constraint(:orders, ...)\nFile Naming\nmix ecto.gen.migration <name>\n# Generates: priv/repo/migrations/YYYYMMDDHHMMSS_<name>.exs\n\n\nName conventions: create_<table>, add_<column>_to_<table>, create_<table>_<column>_index, alter_<table>_add_<columns>.\n\nMigration Template\ndefmodule MyApp.Repo.Migrations.CreateUsers do\n  use Ecto.Migration\n\n  def change do\n    create table(:users, primary_key: false) do\n      add :id, :binary_id, primary_key: true\n      add :email, :string, null: false\n      add :name, :string, null: false\n      add :role, :string, null: false, default: \"member\"\n      add :metadata, :map, default: %{}\n      add :tenant_id, :binary_id, null: false\n\n      add :team_id, references(:teams, type: :binary_id, on_delete: :delete_all)\n\n      timestamps(type: :utc_datetime_usec)\n    end\n\n    create unique_index(:users, [:tenant_id, :email])\n    create index(:users, [:tenant_id])\n    create index(:users, [:team_id])\n  end\nend\n\nColumn Types\n\nSee references/column-types.md for complete type mapping and guidance.\n\nKey decisions:\n\nIDs: Use :binary_id (UUID) — set primary_key: false on table, add :id manually.\nMoney: Use :integer (cents) or :decimal — never :float.\nTimestamps: Always timestamps(type: :utc_datetime_usec).\nEnums: Use :string with app-level Ecto.Enum — avoid Postgres enums (hard to migrate).\nJSON: Use :map (maps to jsonb).\nArrays: Use {:array, :string} etc.\nIndex Strategies\n\nSee references/index-patterns.md for detailed index guidance.\n\nWhen to Add Indexes\n\nAlways index:\n\nForeign keys (_id columns)\ntenant_id (first column in composite indexes)\nColumns used in WHERE clauses\nColumns used in ORDER BY\nUnique constraints\nIndex Types\n# Standard B-tree\ncreate index(:users, [:tenant_id])\n\n# Unique\ncreate unique_index(:users, [:tenant_id, :email])\n\n# Partial (conditional)\ncreate index(:orders, [:status], where: \"status != 'completed'\", name: :orders_active_status_idx)\n\n# GIN for JSONB\ncreate index(:events, [:metadata], using: :gin)\n\n# GIN for array columns\ncreate index(:posts, [:tags], using: :gin)\n\n# Composite\ncreate index(:orders, [:tenant_id, :status, :inserted_at])\n\n# Concurrent (no table lock — use in separate migration)\n@disable_ddl_transaction true\n@disable_migration_lock true\n\ndef change do\n  create index(:users, [:email], concurrently: true)\nend\n\nConstraints\n# Check constraint\ncreate constraint(:orders, :amount_must_be_positive, check: \"amount > 0\")\n\n# Exclusion constraint (requires btree_gist extension)\nexecute \"CREATE EXTENSION IF NOT EXISTS btree_gist\", \"\"\ncreate constraint(:reservations, :no_overlapping_bookings,\n  exclude: ~s|gist (room_id WITH =, tstzrange(starts_at, ends_at) WITH &&)|\n)\n\n# Unique constraint (same as unique_index for most purposes)\ncreate unique_index(:accounts, [:slug])\n\nReferences (Foreign Keys)\nadd :user_id, references(:users, type: :binary_id, on_delete: :delete_all), null: false\nadd :team_id, references(:teams, type: :binary_id, on_delete: :nilify_all)\nadd :parent_id, references(:categories, type: :binary_id, on_delete: :nothing)\n\non_delete\tUse When\n:delete_all\tChild can't exist without parent (memberships, line items)\n:nilify_all\tChild should survive parent deletion (optional association)\n:nothing\tHandle in application code (default)\n:restrict\tPrevent parent deletion if children exist\nMulti-Tenant Patterns\nEvery Table Gets tenant_id\ndef change do\n  create table(:items, primary_key: false) do\n    add :id, :binary_id, primary_key: true\n    add :name, :string, null: false\n    add :tenant_id, :binary_id, null: false\n    timestamps(type: :utc_datetime_usec)\n  end\n\n  # Always composite index with tenant_id first\n  create index(:items, [:tenant_id])\n  create unique_index(:items, [:tenant_id, :name])\nend\n\nAdding tenant_id to Existing Tables\ndef change do\n  alter table(:items) do\n    add :tenant_id, :binary_id\n  end\n\n  # Backfill in a separate data migration, then:\n  # alter table(:items) do\n  #   modify :tenant_id, :binary_id, null: false\n  # end\nend\n\nData Migrations\n\nRule: Never mix schema changes and data changes in the same migration.\n\nSafe Data Migration Pattern\ndefmodule MyApp.Repo.Migrations.BackfillUserRoles do\n  use Ecto.Migration\n\n  # Don't use schema modules — they may change after this migration runs\n  def up do\n    execute \"\"\"\n    UPDATE users SET role = 'member' WHERE role IS NULL\n    \"\"\"\n  end\n\n  def down do\n    # Data migrations may not be reversible\n    :ok\n  end\nend\n\nBatched Data Migration (large tables)\ndef up do\n  execute \"\"\"\n  UPDATE users SET role = 'member'\n  WHERE id IN (\n    SELECT id FROM users WHERE role IS NULL LIMIT 10000\n  )\n  \"\"\"\n\n  # For very large tables, use a Task or Oban job instead\nend\n\nReversible vs Irreversible\nReversible (use change)\n\nThese are auto-reversible:\n\ncreate table ↔ drop table\nadd column ↔ remove column\ncreate index ↔ drop index\nrename ↔ rename\nIrreversible (use up/down)\n\nMust define both directions:\n\nmodify column type — Ecto can't infer the old type\nexecute raw SQL\nData backfills\nDropping columns with data\ndef up do\n  alter table(:users) do\n    modify :email, :citext, from: :string  # from: helps reversibility\n  end\nend\n\ndef down do\n  alter table(:users) do\n    modify :email, :string, from: :citext\n  end\nend\n\nUsing modify with from:\n\nPhoenix 1.7+ supports from: for reversible modify:\n\ndef change do\n  alter table(:users) do\n    modify :email, :citext, null: false, from: {:string, null: true}\n  end\nend\n\nPostgreSQL Extensions\ndef change do\n  execute \"CREATE EXTENSION IF NOT EXISTS citext\", \"DROP EXTENSION IF EXISTS citext\"\n  execute \"CREATE EXTENSION IF NOT EXISTS pgcrypto\", \"DROP EXTENSION IF EXISTS pgcrypto\"\n  execute \"CREATE EXTENSION IF NOT EXISTS pg_trgm\", \"DROP EXTENSION IF EXISTS pg_trgm\"\nend\n\nEnum Types (PostgreSQL native — use sparingly)\n\nPrefer Ecto.Enum with :string columns. If you must use Postgres enums:\n\ndef up do\n  execute \"CREATE TYPE order_status AS ENUM ('pending', 'confirmed', 'shipped', 'delivered')\"\n\n  alter table(:orders) do\n    add :status, :order_status, null: false, default: \"pending\"\n  end\nend\n\ndef down do\n  alter table(:orders) do\n    remove :status\n  end\n\n  execute \"DROP TYPE order_status\"\nend\n\n\nWarning: Adding values to Postgres enums requires ALTER TYPE ... ADD VALUE which cannot run inside a transaction. Prefer :string + Ecto.Enum.\n\nChecklist\n Primary key: primary_key: false + add :id, :binary_id, primary_key: true\n null: false on required columns\n timestamps(type: :utc_datetime_usec)\n Foreign keys with appropriate on_delete\n Index on every foreign key column\n tenant_id indexed (composite with lookup fields)\n Unique constraints where needed\n Concurrent indexes in separate migration with @disable_ddl_transaction true\n Data migrations in separate files from schema migrations"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/gchapim/ecto-migrator",
    "publisherUrl": "https://clawhub.ai/gchapim/ecto-migrator",
    "owner": "gchapim",
    "version": "1.0.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/ecto-migrator",
    "downloadUrl": "https://openagent3.xyz/downloads/ecto-migrator",
    "agentUrl": "https://openagent3.xyz/skills/ecto-migrator/agent",
    "manifestUrl": "https://openagent3.xyz/skills/ecto-migrator/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/ecto-migrator/agent.md"
  }
}