{"owner":"Canner","repo":"WrenAI","hasSkills":true,"totalSkillsCount":16,"totalTokensCount":27590,"categories":["claude-rule","root-instruction","anthropic-skill","marketplace","plugin-manifest"],"hasMcp":false,"mcpConfig":null,"found":[".claude/CLAUDE.md","AGENTS.md","core/wren/src/wren/skills_content/dlt-connector/SKILL.md","core/wren/src/wren/skills_content/enrich-context/SKILL.md","core/wren/src/wren/skills_content/genbi/SKILL.md","core/wren/src/wren/skills_content/generate-mdl/SKILL.md","core/wren/src/wren/skills_content/onboarding/SKILL.md","core/wren/src/wren/skills_content/usage/SKILL.md","evals/spodbtify_ab/README.md","skills/.claude-plugin/marketplace.json","skills/.claude-plugin/plugin.json","skills/AUTHORING.md","skills/README.md","skills/SKILLS.md","skills/index.json","skills/wren/SKILL.md"],"skills":{".claude/CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nWren is an open-source semantic engine for MCP clients and AI agents. It translates SQL queries through a semantic layer (MDL — Modeling Definition Language) and executes them against 22+ data sources (PostgreSQL, BigQuery, Snowflake, Spark, etc.). The Rust engine is powered by Apache DataFusion (upstream, crates.io v55).\n\nThe previous WrenAI services (`wren-ui/`, `wren-ai-service/`, `wren-launcher/`, `docker/`, `deployment/`) were moved to the `legacy/v1` branch (tag `v1-final`) as of the wren-engine import. Active development is focused on the Open Context Engine.\n\n## Repository Structure\n\n```\ncore/\n├── wren-core/        Rust semantic engine (Cargo workspace; crates.io: wren-semantic-core, lib name wren_core)\n├── wren-core-base/   Shared Rust crate — manifest types (Model, Column, Metric, Relationship, View) + ManifestBuilder (crates.io: wren-core-base)\n├── wren-core-py/     PyO3 bindings exposing wren-core to Python (PyPI: wren-core)\n├── wren-core-wasm/   WebAssembly build of wren-core for in-browser semantic SQL (npm: wren-core-wasm)\n├── wren/             Python SDK and CLI — `wren` command, profile/context/memory management (PyPI: wrenai)\n└── wren-mdl/         MDL JSON schema definition\n\ndocs/core/            Module documentation\nexamples/             Example projects (placeholder — to be populated)\nskills/               CLI-based agent skills (wren-generate-mdl, wren-usage, wren-dlt-connector, wren-onboarding)\nscripts/              Repo helper scripts\n```\n\n## Build & Development Commands\n\n### core/wren-core (Rust)\n```bash\ncd core/wren-core\ncargo check --all-targets                                  # compile check\ncargo test --lib --tests --bins                            # tests (set RUST_MIN_STACK=8388608)\ncargo fmt --all                                            # format\ncargo clippy --all-targets --all-features -- -D warnings   # lint\ntaplo fmt                                                  # format Cargo.toml files\n```\n\nMost unit tests live in `core/wren-core/core/src/mdl/mod.rs`. SQL end-to-end tests use sqllogictest files in `core/wren-core/sqllogictest/test_files/`.\n\n### core/wren-core-py (Python bindings)\n```bash\ncd core/wren-core-py\njust install      # uv sync (deps only; --no-install-project)\njust develop      # build dev wheel with maturin\njust test-rs      # Rust tests (cargo test --no-default-features)\njust test-py      # Python tests (pytest)\njust test         # both\njust format       # cargo fmt + ruff + taplo\n```\n\n### core/wren-core-wasm (WASM)\n```bash\ncd core/wren-core-wasm\njust build        # wasm-pack build (browser target)\njust test         # wasm-pack test\n```\nOutputs a ~68 MB WASM binary; distributed via npm and unpkg (jsDelivr's 50 MB per-file CDN limit blocks it).\n\n### core/wren (SDK & CLI)\n```bash\ncd core/wren\njust install              # uv sync (locked prebuilt wren-core-py wheel from PyPI; no Rust build)\njust install-all          # with all optional extras (incl. memory)\njust install-extra <e>    # e.g. just install-extra postgres\njust install-memory       # memory extra (lancedb + sentence-transformers)\njust install-local        # engine dev: uv sync + build local wheel + overlay\njust use-local-core       # rebuild + re-overlay after Rust changes\njust dev                  # run `wren` CLI\njust test                 # pytest tests/\njust test-memory          # memory-specific tests\njust lint                 # ruff format --check + ruff check\njust format               # ruff auto-fix\njust build                # uv build (produces wheel)\n```\n\nUses `uv` (not Poetry). `pyproject.toml` uses `hatchling` as build backend. Optional extras: `postgres`, `mysql`, `bigquery`, `snowflake`, `clickhouse`, `trino`, `mssql`, `databricks`, `redshift`, `spark`, `athena`, `oracle`, `memory`, `all`, `dev`.\n\n## Architecture: Query Flow\n\n```\nSQL query\n  → wren CLI / wren-core-py\n  → wren-core (Rust): MDL analysis → logical plan → optimization\n  → DataFusion (query planning, upstream crates.io v55)\n  → connector-specific SQL (Ibis / sqlglot)\n  → native execution on the target data source\n```\n\n## Key Architecture Details\n\n**wren-core internals** (`core/wren-core/core/src/`):\n- `mdl/` — Core MDL processing: `WrenMDL` (manifest + symbol table), `AnalyzedWrenMDL` (with lineage), function definitions per dialect (scalar/aggregate/window), type planning\n- `logical_plan/analyze/` — DataFusion analyzer rules: `ModelAnalyzeRule` (TableScan → ModelPlanNode), scope tracking, access control (RLAC/CLAC), view expansion, relationship chain resolution\n- `logical_plan/optimize/` — Optimization passes: type coercion, timestamp simplification\n- `sql/` — SQL parsing and analysis\n\n**Manifest types** (`core/wren-core-base/src/mdl/`):\n- `manifest.rs` — `Manifest`, `Model`, `Column`, `Metric`, `Relationship`, `View`, `RowLevelAccessControl`, `ColumnLevelAccessControl`\n- `builder.rs` — Fluent `ManifestBuilder` API\n- Uses `wren-manifest-macro` for auto-generating Pydantic-compatible Python classes\n\n## Known wren-core Limitations\n\n**ModelAnalyzeRule — correlated subquery column resolution**: cannot resolve outer column references inside correlated subqueries; only sees the subquery's own table scope. Affects TPCH Q2, Q4, Q15, Q17, Q20, Q21, Q22.\n\n## Conventions\n\n- **Commits**: Conventional commits (`feat:`, `fix:`, `chore:`, `refactor:`, `test:`, `docs:`, `perf:`, `deps:`). Releases are automated via release-please with independent release lines per module.\n- **Rust**: format with `cargo fmt`, lint with `clippy -D warnings`, TOML with `taplo`.\n- **Python**: format and lint with `ruff` (line-length 88, target Python 3.11). Both `core/wren-core-py` and `core/wren` use uv.\n- **DataFusion**: upstream `datafusion` v55 from crates.io (no longer the Canner fork).\n- **Snapshot testing**: wren-core uses `insta` for Rust snapshot tests.\n- **CI**: Per-module path-filtered workflows trigger only on changes inside that module.\n\n## Contribution Bar\n\nRead this before opening a pull request. These rules exist because a large share of\nincoming PRs fail them; a PR that violates one will be closed rather than iterated on.\n\n### Prove the problem before fixing it\n\n- **Do not assert the behaviour of an external system you have not observed.** If you\n  claim an engine, driver, or library rejects some input, reproduce it and paste the\n  actual error into the description. Unevidenced claims — \"the parser rejects a trailing\n  semicolon\", \"some clients reject this\" — are rejected on sight; several such claims\n  have turned out to be false when tested.\n- **Do not add a guard for a state you have not shown to be reachable.** Trace the call\n  path and say in the description how the bad value gets there. Where the repo already\n  validates (see \"Validation boundaries\" in `core/wren/.claude/CLAUDE.md`), re-validating\n  downstream is dead code.\n- If you cannot reproduce a failure, you have a hypothesis, not a bug. **Open an issue\n  with the reproduction attempt, not a PR.**\n\n### Label honestly\n\n- `fix:` requires a reproducible failure that the change repairs. A change with no\n  observable difference in behaviour is `refactor:` or `chore:`.\n- The description must say what changes for a user. \"Improves robustness\" and\n  \"hardens X\" are not answers.\n\n### Tests must exercise the code you changed\n\n- A test that reads a source file as text and asserts on substrings tests nothing. Call\n  the function; assert on what it returns, or on what it passes downstream.\n- Every assertion must be capable of failing. An assertion about a condition the code\n  cannot produce is noise.\n- Confirm the test actually runs in CI. Some test files are gated behind optional extras\n  or containers and are skipped by the default jobs — a service-free test placed in one\n  of those files never executes.\n\n### One change, once\n\n- Search open PRs before opening a new one. Duplicate PRs against the same file are\n  closed without review.\n- A mechanical change repeated across several files or connectors belongs in **one** PR,\n  not one PR per file. Reviewers need to see the resulting convention in a single diff.\n\n### Respect decisions already recorded\n\n- If a comment or a test documents a deliberate choice, do not silently reverse it.\n  Quote it in the description and argue why it should change.\n\n### Keep the branch current\n\n- Rebase onto the target branch before requesting review. A branch whose last CI run was\n  against an old base can merge cleanly and still be broken — a textually clean merge is\n  not a semantically correct one.\n","AGENTS.md":"# AGENTS.md\n\nRepository-wide instructions live in `.claude/CLAUDE.md`. Please refer to that file for shared build commands, architecture, and conventions.\n\n**Before opening a pull request, read the \"Contribution Bar\" section of that file.** It sets out what is expected of a change here — reproduce a failure before claiming one, label honestly, make tests exercise the code they cover — and what will be closed rather than reviewed.\n\nPer-module instructions live in `core/<module>/.claude/CLAUDE.md`, each with its own `AGENTS.md` pointing at it.\n","core/wren/src/wren/skills_content/dlt-connector/SKILL.md":"---\nname: dlt-connector\ndescription: \"Connect SaaS data (HubSpot, Stripe, Salesforce, GitHub, Slack, etc.) to Wren Engine for SQL analysis. Guides the user through the full flow: install dlt, pick a SaaS source, set up credentials, run the data pipeline into DuckDB, then auto-generate a Wren semantic project from the loaded data. Use this skill whenever the user mentions: connecting SaaS data, importing data from an API, dlt pipelines, loading HubSpot/Stripe/Salesforce/GitHub/Slack data, querying SaaS data with SQL, or setting up a new data source from a REST API. Also trigger when the user already has a dlt-produced DuckDB file and wants to create a Wren project from it.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# wren-dlt-connector\n\n> Reference docs (`dlt_sources`) and the `introspect_dlt` script are bundled. Pull references with `wren skills get dlt-connector --full`; fetch a script with `wren skills get dlt-connector --script <name>`.\n\nConnect SaaS data to Wren Engine for SQL analysis — from zero to a verified, queryable project in one conversation.\n\n## Who this is for\n\nData analysts who know SQL and some Python, but may not have used dlt or Wren before. Explain concepts briefly when they first appear, but don't over-explain things a SQL-literate person would already know.\n\n## Overview\n\nThis skill walks through a four-phase workflow:\n\n1. **Extract** — Use dlt (data load tool) to pull data from a SaaS API into a local DuckDB file\n2. **Model** — Introspect the DuckDB schema and auto-generate a Wren semantic project (YAML models, relationships, profile)\n3. **Build & Verify** — Build the project and run actual SQL queries to confirm everything works end-to-end\n4. **Handoff** — Show the user their data and next steps\n\nThe user might enter at any phase. Ask which phase they're starting from — they may already have a `.duckdb` file and just need phases 2–4.\n\n**The goal is a project that actually queries successfully, not just files that look correct.** Always run the verification step before declaring success.\n\n## Critical: DuckDB catalog naming\n\nWhen wren engine connects to a DuckDB file, it ATTACHes it using the filename (without `.duckdb` extension) as the catalog alias:\n\n```\nATTACH DATABASE 'stripe_data.duckdb' AS \"stripe_data\" (READ_ONLY)\n```\n\nThis means **every model's `table_reference.catalog` must equal the DuckDB filename stem**. If the file is `hubspot.duckdb`, the catalog is `hubspot`. If it's `my_pipeline.duckdb`, the catalog is `my_pipeline`.\n\nGetting this wrong causes \"table not found\" errors at query time. The `introspect_dlt.py` script handles this automatically.\n\n## Critical: Type normalization\n\nColumn types must be normalized using wren SDK's `type_mapping.parse_type()` function, which uses sqlglot to convert database-specific types (like DuckDB's `HUGEINT`, `TIMESTAMP WITH TIME ZONE`) into canonical SQL types that wren-core understands. Do not hardcode type mappings — always delegate to `parse_type(raw_type, \"duckdb\")`.\n\nThe `introspect_dlt.py` script does this automatically when wren SDK is installed.\n\n## Phase 1: Extract — dlt Pipeline Setup\n\n### Step 1: Pick the SaaS source\n\nAsk the user which SaaS service they want to connect. Read `dlt_sources` for a list of popular verified sources and their auth requirements. If the source isn't listed, check whether dlt has a verified source for it by searching `dlthub.com/docs/dlt-ecosystem/verified-sources`.\n\n### Step 2: Install dlt\n\n```bash\npip install \"dlt[duckdb]\" --break-system-packages\n```\n\n### Step 3: Write the pipeline script\n\nCreate a Python script that:\n1. Imports the dlt source function for the chosen SaaS\n2. Configures the pipeline with `destination='duckdb'` and a local file path\n3. Runs the pipeline with `pipeline.run(source)`\n\nHere's the general pattern — adapt it per source (check `dlt_sources` for source-specific templates):\n\n```python\nimport dlt\n\npipeline = dlt.pipeline(\n    pipeline_name=\"<source>_pipeline\",\n    destination=\"duckdb\",\n    dataset_name=\"<source>_data\",\n)\n\n# Source-specific: check the dlt_sources reference for auth patterns\nsource = <source_function>(api_key=dlt.secrets.value)\n\ninfo = pipeline.run(source)\nprint(info)\n```\n\n### Step 4: Set up credentials\n\ndlt reads credentials from environment variables or `.dlt/secrets.toml`. The simplest approach for a one-time run:\n\n```bash\n# Set the credential as an environment variable\n# The exact variable name depends on the source — check the dlt_sources reference\nexport SOURCES__<SOURCE>__API_KEY=\"the-actual-key\"\n```\n\nAsk the user for their API key or token. Remind them:\n- Never commit credentials to git\n- Environment variables are the simplest way for a one-time run\n- For repeated use, they can create `.dlt/secrets.toml`\n\n### Step 5: Run the pipeline\n\n```bash\npython <pipeline_script>.py\n```\n\nAfter the run, confirm:\n1. The pipeline completed without errors\n2. A `.duckdb` file was created (usually at `<pipeline_name>.duckdb`)\n3. Print discovered tables and their column counts\n\n```python\nimport duckdb\n\ncon = duckdb.connect(\"<pipeline_name>.duckdb\", read_only=True)\nfor row in con.execute(\"\"\"\n    SELECT table_schema, table_name,\n           (SELECT COUNT(*) FROM information_schema.columns c\n            WHERE c.table_schema = t.table_schema AND c.table_name = t.table_name) as col_count\n    FROM information_schema.tables t\n    WHERE table_schema NOT IN ('information_schema', 'pg_catalog')\n      AND table_name NOT LIKE '_dlt_%'\n    ORDER BY table_schema, table_name\n\"\"\").fetchall():\n    print(f\"  {row[0]}.{row[1]} ({row[2]} columns)\")\ncon.close()\n```\n\n## Phase 2: Model — Generate Wren Project\n\nRun the introspection script to auto-generate a complete Wren project from the DuckDB file:\n\n```bash\n# first fetch the script: wren skills get dlt-connector --script introspect_dlt > introspect_dlt.py\npython introspect_dlt.py \\\n    --duckdb-path <path-to-duckdb-file> \\\n    --output-dir <project-directory> \\\n    --project-name <name>\n```\n\nThis script:\n- Connects to the DuckDB file (read-only)\n- **Sets `table_reference.catalog` to the DuckDB filename stem** (matching wren engine's ATTACH behavior)\n- Discovers all tables and columns via `information_schema`\n- Filters out dlt internal tables (`_dlt_loads`, `_dlt_pipeline_state`, etc.)\n- Filters out dlt metadata columns (`_dlt_id`, `_dlt_load_id`, `_dlt_list_idx`) from model definitions\n- Detects parent-child relationships from `_dlt_parent_id` columns and table naming conventions\n- **Normalizes column types using `wren.type_mapping.parse_type()`** (sqlglot-based)\n- Generates a complete v5 YAML project (wren_project.yml, models/, relationships.yml, knowledge/rules/)\n\nAfter running, show the user what was generated:\n\n```bash\n# Show project summary\ncat <project-directory>/wren_project.yml\necho \"---\"\nls <project-directory>/models/\necho \"---\"\ncat <project-directory>/relationships.yml\n```\n\n### Verify model correctness\n\nSpot-check one generated model to confirm:\n1. `table_reference.catalog` matches the DuckDB filename (e.g., `stripe_data` for `stripe_data.duckdb`)\n2. `table_reference.schema` matches the DuckDB schema (usually `main`)\n3. No `_dlt_*` columns appear in the columns list\n4. Column types look reasonable (VARCHAR, BIGINT, BOOLEAN, TIMESTAMP, etc.)\n\n### Set up the connection profile\n\nCreate a Wren profile so the user can query without specifying connection details every time. The `url` must point to the **directory containing** the `.duckdb` file (not the file itself):\n\n```python\nimport yaml\nfrom pathlib import Path\n\nwren_home = Path.home() / \".wren\"\nwren_home.mkdir(exist_ok=True)\nprofiles_file = wren_home / \"profiles.yml\"\n\nexisting = (\n    (yaml.safe_load(profiles_file.read_text()) or {}) if profiles_file.exists() else {}\n)\nexisting.setdefault(\"profiles\", {})\n\nprofile_name = \"<source>_dlt\"\nexisting[\"profiles\"][profile_name] = {\n    \"datasource\": \"duckdb\",\n    \"url\": str(Path(\"<duckdb-path>\").resolve().parent),\n    \"format\": \"duckdb\",\n}\nexisting[\"active\"] = profile_name\n\nprofiles_file.write_text(yaml.dump(existing, default_flow_style=False, sort_keys=False))\n```\n\n## Phase 3: Build & Verify — The Project Must Actually Work\n\nThis phase is not optional. A project that generates YAML but fails at query time is not a success.\n\n### Step 1: Build the MDL\n\n```bash\ncd <project-directory>\nwren context build\n```\n\nThis compiles the YAML models into `target/mdl.json`. If this fails, fix the issues before proceeding (see Troubleshooting below).\n\n### Step 2: Validate with a real query\n\nRun at least one query per generated model to confirm the project is functional:\n\n```bash\n# For each model, verify it resolves correctly\nwren --sql 'SELECT COUNT(*) as total FROM \"<table_name>\"'\n```\n\nIf any query fails, debug and fix the model before moving on. Common issues:\n- Wrong catalog in table_reference → \"table not found\"\n- Type mismatch → fix the column type in metadata.yml\n- Missing profile → check `wren profile list`\n\n### Step 3: Run interesting queries\n\nOnce basic queries pass, run 2–3 more interesting queries to show the user what their data looks like:\n\n```bash\n# Preview data\nwren --sql 'SELECT * FROM \"<table_name>\" LIMIT 5'\n\n# If there's a relationship, verify both models are queryable\nwren --sql 'SELECT * FROM \"<parent>\" LIMIT 5'\nwren --sql 'SELECT * FROM \"<child>\" LIMIT 5'\n```\n\nShow the results to the user and explain what they're seeing. This is their first look at the data through Wren — make it count.\n\n### Step 4: Confirm success\n\nOnly after queries return real data, tell the user the setup is complete. Summarize:\n- How many models were created\n- What relationships were detected\n- Which profile is active\n- Example queries they can try next\n\n### Next step: share it as an app\n\nThe project is now DuckDB-backed, which is exactly what GenBI snapshot mode\nwants. If the user wants to turn this data into a shareable dashboard / web app\nand deploy it (Vercel / Cloudflare), hand off to the GenBI workflow:\n`wren skills get genbi`. Its snapshot source is the very `.duckdb` file this\npipeline produced.\n\n## Troubleshooting\n\nIf `wren context build` fails:\n- Check that `data_source: duckdb` is set in `wren_project.yml`\n- Verify the DuckDB file path in the profile is correct\n- Run `wren context validate` for detailed error messages\n\nIf queries fail with \"table not found\":\n- **Most likely cause:** `table_reference.catalog` doesn't match the DuckDB filename. If the file is `pipeline.duckdb`, the catalog must be `pipeline`, not empty string.\n- Check the profile's `url` points to the directory containing the `.duckdb` file\n- Table names with double underscores need quoting: `\"hubspot__contacts\"`\n\nIf queries fail with type errors:\n- Check column types in the model YAML — they should be canonical SQL types (VARCHAR, BIGINT, etc.)\n- Re-run `introspect_dlt.py` with wren SDK installed to get proper type normalization\n\nGeneral:\n- Check that the profile is active: `wren profile list`\n- The DuckDB file might be locked if a dlt pipeline is running — wait for it to finish\n\n## Important notes\n\n- dlt's `_dlt_parent_id` / `_dlt_id` columns are kept in the actual DuckDB tables but hidden from Wren model definitions. They're only used in relationship conditions.\n- DuckDB has a single-writer limitation. Don't run a dlt sync while querying. For concurrent access, dlt should write to a separate file and swap atomically.\n- The generated models use `table_reference` (not `ref_sql`) since they map directly to DuckDB tables created by dlt.\n- Column types are normalized using wren SDK's `parse_type()` with sqlglot's DuckDB dialect. If a type looks wrong, the user can edit the model's `metadata.yml` directly.\n","core/wren/src/wren/skills_content/enrich-context/SKILL.md":"---\nname: enrich-context\ndescription: \"Augment a Wren project with business context that DB schema cannot carry — enum value meanings, units (USD vs cents, ms vs sec), NULL semantics, magic sentinels (-1 = unknown), soft-delete default filters, business synonyms, time-grain / TZ conventions, cross-system identifiers, currency rules, canonical-table preferences, AND named aggregation metrics (ARR, churn, DAU, WAU, NRR) proposed as cubes. Runs in one of two modes selected at session start: `grill` (one question at a time, user-driven) or `auto-pilot` (agent infers and applies, escalates only on conflicts and high-blast-radius additions like new cubes / views / relationships). Reads everything under <project>/raw/ (PDFs, glossaries, handbooks, code, data dictionaries) and optionally samples low-cardinality columns from the live DB (grill mode), compares against the current MDL / cubes / knowledge (rules + NL→SQL pairs), then fills gaps via the ten-category gap catalog and the cube proposal flow. Confirmed findings are written back to the right sink. Use when: user says 'enrich context', 'augment my project', 'grill me on this project', 'auto-fill my context', 'agent doesn't understand our docs / enum values / units / null meanings', 'business context is missing', 'what does status=A mean', 'is this amount in USD or cents', 'we keep getting wrong aggregations', 'add cubes for ARR / DAU / churn', 'we have a handbook / glossary / data dictionary the agent should know'; or after generating an MDL and noticing the agent lacks business semantics.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren Enrich Context — Fill the Business-Context Gap\n\nThis skill exists because most business context never lives in a DB schema — it lives in handbooks, glossaries, finance reports, support playbooks, code comments, Slack rules-of-thumb. The agent reads those raw artifacts, finds what's missing from the Wren project, and **either grills the user one question at a time (grill mode) or applies its best inferences directly and hands over an audit (auto-pilot mode)** before writing back. The output lands in the sinks each project already has — MDL, `cubes/`, `knowledge/rules/`, and `knowledge/sql/` — no new artifact, no new tooling.\n\n## Hard rules — READ FIRST\n\n### Universal (apply to both modes)\n\n1. **Only add, never modify existing.** If you find an existing MDL description / relationship / rule that looks wrong, **do not edit it**. Surface it on the \"please fix manually\" list shown in Step 9.\n2. **Every MDL edit must validate.** Right after any MDL YAML change, run `wren context validate`. If it fails, **revert that single change** and feed the error back. Never leave a project in an invalid state.\n3. **Pre-draft every proposal.** Whether you're showing the draft to the user (grill) or applying it directly (auto-pilot), generate the concrete content — never lazy-ask \"what should the description say?\".\n4. **Be explicit about confidence.** In grill mode, open Lane 3 inference questions with \"I'm guessing — \". In auto-pilot, tag every Lane 3 inference and partial Lane 2 match in the Step 9 audit with confidence (high / med / low) and source.\n\n### Grill mode only\n\n5. **One question at a time. Grill relentlessly.** Walk every gap top-down, resolve one decision before moving to the next. Provide a recommended answer for every question.\n6. **Skip is final for this session.** No pending queue, no nagging next round. If the user wants to revisit, they re-run the skill.\n\n### Auto-pilot mode only\n\n7. **Drop into grill for three cases.** Always interrupt auto-pilot and ask the user when:\n   - (a) **Lane 2 conflict** — raw and current MDL disagree.\n   - (b) **High-blast-radius proposal (any lane)** — new cube, new view, new relationship, or new MDL metric/calculated column. These become public artifacts visible to every future agent session, so blast radius doesn't depend on whether the trigger was raw evidence (Lane 2) or inference (Lane 3).\n   - (c) **Lane 2 routing ambiguity** — you can't confidently pick a sink (MDL / `knowledge/rules/` / `knowledge/sql/` / `cubes/`).\n\n   Everything else: apply directly and log to the audit list.\n\n## Step 0 — Mode selection (before anything else)\n\nBefore touching the project or reading any file, ask the user which mode to run in. Lock the choice for the whole session — **no mid-session switching**; the user re-runs to change.\n\n> Two modes for this session:\n>\n> **a) Grill mode** — I walk every gap with you, one question at a time, proposing a draft and waiting for your accept / edit / skip. You stay in the driver's seat. Best when the raw material is sensitive, when you want to learn what I don't know about your project, or when you'd rather review than re-do.\n>\n> **b) Auto-pilot mode** — I read raw + current context, make my best inferences, and apply them. I'll only stop to grill you on (1) conflicts between raw and existing MDL and (2) high-blast-radius additions like new metrics, views, or relationships. The session ends with a full diff + confidence-tagged inference list for you to audit.\n>\n> Which? (a / b)\n\nRemember the choice as `MODE = grill | autopilot` and use it to branch Steps 6 and 9.\n\n## Preflight\n\n### Step 1 — Choose the Wren project\n\n**Always ask the user which project to enrich before doing anything else** — never assume cwd. A user can have several Wren projects and an ambient `~/.wren` profile that doesn't match the one they want to augment.\n\nOffer concrete hints in the question so the user can answer in one round-trip:\n\n```bash\n# Hint 1 — does cwd look like a project?\ntest -f wren_project.yml && pwd\n\n# Hint 2 — does ~/.wren/config.yml point at a default project?\ngrep -E '^project_path:' ~/.wren/config.yml 2>/dev/null\n```\n\nThen ask:\n\n> Which Wren project do you want me to augment?\n> a) `$PWD` (current directory)         ← if Hint 1 matched\n> b) `<path from ~/.wren/config.yml>`   ← if Hint 2 matched\n> c) something else — paste the absolute path\n\nAfter the user answers, lock the path in for the whole session:\n\n```bash\ncd <chosen-path>\ntest -f wren_project.yml || {\n  echo \"Error: <chosen-path> is not a Wren project (no wren_project.yml).\"\n  exit 1\n}\nwren context show >/dev/null 2>&1 || {\n  echo \"Error: wren context show failed — manifest may be invalid.\"\n  exit 1\n}\n```\n\nIf either check fails, stop and tell the user — suggest `wren skills get onboarding` if it's not a project, or `wren context validate` if the manifest is broken.\n\nFrom this point on, **every command and file path in this skill is relative to the chosen project root**. Do not switch projects mid-session — if the user wants to work a different project, end this session and re-run.\n\n### Step 2 — Detect semantic-memory availability\n\n```bash\nwren memory status 2>/dev/null | grep -q \"Backend: lancedb\"\n```\n\nWriting NL→SQL pairs (`wren memory store` → `knowledge/sql/*.md`) works **regardless** — that\nsink is always open. This checks which recall backend is active, i.e. whether the optional\n`memory` extra is installed (LanceDB = semantic; grep = dependency-free fallback):\n\n- Match (`Backend: lancedb`) → set `MEMORY_AVAILABLE = true`. Semantic recall and `wren memory fetch` are usable, and `wren memory index` builds an embedding index in Step 8.\n- No match (grep backend) → set `MEMORY_AVAILABLE = false`. Skip the semantic read/index paths below; pair writeback still happens via `wren memory store`, and grep recall still works.\n\n### Step 3 — Ensure raw/ folder exists\n\nFrom the project root (cwd is already there from Step 1):\n\n```bash\nmkdir -p raw\n```\n\nIf you just created it (the directory was empty or new):\n\n> I've created `raw/` at the project root. Drop anything you think helps explain this project's business context — PDFs, glossaries, handbooks, financial reports, data dictionaries, sample queries, code with comments, screenshots of dashboards, anything.\n>\n> **Heads-up:** the contents may be sensitive. Decide for yourself whether to commit `raw/` to git — I won't touch `.gitignore`.\n>\n> Tell me when you've added the files and I'll start reading.\n\nWait for the user to confirm before continuing.\n\n## Step 4 — Read everything\n\nRead both sides — the raw material and the current Wren context — before forming any opinion.\n\n### Raw\n\nRead every file under `raw/`. Use whatever capability your agent has natively (text, markdown, code, PDF). If you genuinely can't read a particular file, **tell the user once** which file and suggest converting it to text or pasting the relevant excerpt — then move on to the rest. Do **not** install extra Python packages, **do not** reach for new CLI subcommands.\n\n### Current Wren context\n\n| Source | Command |\n|---|---|\n| MDL (full) | `wren context show --output json` |\n| Business rules | `wren context instructions` (reads `knowledge/rules/` + any legacy `instructions.md`) |\n| Existing cubes (names) | `wren cube list` |\n| Existing cubes (measures + dimensions) | `wren cube describe <cube>` for each name above |\n| NL→SQL pairs | read `knowledge/sql/*.md` directly |\n| (Memory) stored pairs | `wren memory list -n 200 --output json` |\n| (Memory) schema as text | `wren memory describe` |\n\nThe memory rows only matter when `MEMORY_AVAILABLE = true`. Reading cubes is essential before any Lane 3 metric proposal — see `cube_proposals` for the duplication guard.\n\n## Step 4.5 — Ground-truth probe (grill mode default; auto-pilot opt-out)\n\nWhen raw is silent on a column's enum / unit / null / magic / time semantics, the catalog's column-local categories (#1, #2, #3, #5, #7 in `gap_catalog`) can often be settled directly by sampling distinct values from the live DB. Read `gap_catalog` before this step — its *Trigger* column tells you which columns are probe candidates.\n\n**Default policy by mode:**\n\n| Mode | Default | How to override |\n|---|---|---|\n| **Grill** | Probe on. Before the first query, ask the user once: \"I want to sample N columns with `LIMIT 30` each to find enum / sentinel / time-grain values — OK?\" Lock the answer for the session. | User says no → skip Step 4.5 entirely; rely on Lane 2 + Lane 3 instead. |\n| **Auto-pilot** | Probe off. The skill never queries the live DB in auto-pilot mode. | None — user must re-run in grill mode if probe would unblock high-confidence inferences. |\n\n**Candidate selection (no DB call yet):**\n\nA column is a probe candidate when *all* hold:\n\n- Description is empty OR does not yet contain the relevant `[tag]` (catalog write format).\n- Column type / name pattern matches catalog #1 (enum), #3 (NULL), #5 (magic), or #7 (time grain).\n- For #3 and #7, the description also lacks event-vs-record or TZ wording.\n\nCategories #2 (unit), #4, #6, #8, #9, #10 are **not** probable — `SELECT DISTINCT` doesn't reveal units, default filters, synonyms, external mappings, currency conventions, or canonical-table preferences. Those need raw evidence or human judgment.\n\n**Probe query:**\n\n```bash\nwren --sql \"SELECT DISTINCT <col> FROM <model> LIMIT 30\" --output json\n# For magic sentinels (catalog #5), also fetch min/max:\nwren --sql \"SELECT MIN(<col>) AS lo, MAX(<col>) AS hi FROM <model>\" --output json\n```\n\n- **≤ 30 distinct values returned** → enum / sentinel / grain candidate. Draft the `[tag]` line and surface to user (grill) with confidence \"med — probed values, semantics still inferred\".\n- **30 returned (LIMIT hit)** → cardinality too high; not an enum / sentinel candidate. Skip.\n- **Query fails** (permissions, connection, large-table timeout) → do not retry. Log the failure to the audit list, surface in Step 9, continue with Lane 2 + Lane 3 only.\n\n**Safety:**\n\n- Probe each (model, column) at most once per session.\n- Never probe a column that already has a matching `[tag]` line — Universal Rule 1.\n- Probe results stay in working memory; do not write them to disk.\n\n## Step 5 — Three gap-detection lanes (in your head, no artifact)\n\nHold all three lanes in working memory. Do not write a `gaps.yml`.\n\nBefore sweeping, load `gap_catalog` — the ten business-semantic categories the schema cannot carry. Each lane consumes the catalog differently: Lane 1 walks it as type-aware mechanical triggers, Lane 2 classifies each atomic raw claim into one of the 10 categories before routing, Lane 3 seeds inference prompts when a trigger fires but raw is silent.\n\n### Lane 1 — Structural coverage (mechanical)\n\nScan the current MDL and check:\n\n- Every model has a non-empty `properties.description`?\n- Every column has a description (at least for non-PK, non-FK ones)?\n- Every model has a `primary_key`?\n- Every model has at least one relationship (orphan models are suspicious)?\n- `knowledge/rules/` has real content beyond the scaffold default?\n- `knowledge/sql/` has at least a few canonical NL→SQL pairs?\n\nPlus, walk every column / model against `gap_catalog` triggers:\n\n- For each column matching catalog #1 / #2 / #3 / #5 / #7 triggers → is the corresponding `[tag]` line present in `properties.description`?\n- For each model with a soft-delete column (`deleted_at`, `is_active`, `archived_at`, etc.) → is there a `## Default filters` rule in `knowledge/rules/` covering it (catalog #4)?\n- For each lookalike table pair (e.g. `users` / `users_v3`) → is there a `## Canonical tables` rule (catalog #10)?\n- For each `*_currency` / `fx_rate` / external-system ID column → is the matching `## Currency` (#9) or `## External identifiers` (#8) section present?\n- Business terms in `knowledge/rules/` or raw that don't map verbatim to model / column names → catalog #6 `## Naming conventions` rule missing.\n\nEach unsatisfied check is a candidate. Combine with Step 4.5 probe results (if available) before moving to Lane 2.\n\n### Lane 2 — Claim-diff (raw vs current context)\n\nFor each raw file, internally extract 5–15 **atomic claims** — single statements that could be true or false, e.g. \"an order has exactly one customer\", \"user means type=default by default\", \"ARR equals MRR × 12 minus refunds\". Then for each claim, classify against the current Wren context:\n\n| Class | Meaning | Resolution outcome |\n|---|---|---|\n| **covered** | already reflected in MDL / instructions / pairs | skip |\n| **partial** | the topic exists but the wording / scope differs | propose tightening |\n| **new** | nothing in current context matches | route to a sink |\n| **conflict** | raw says A, current context says B | grill the user (both modes), **but do not edit existing** — surface for manual fix |\n\n### Lane 3 — Inference (your own guesses)\n\nAfter reading raw and the current MDL, propose additions the user did **not** literally state in raw but that would clearly help the agent later. Examples:\n\n- \"I see `quarterly_churn` referenced five times in `finance.pdf`. No existing cube covers it. Want me to add `cubes/quarterly_churn/metadata.yml` with measure = `COUNT(*) FILTER (WHERE churned_at IS NOT NULL) / NULLIF(COUNT(*), 0)`?\" — see `cube_proposals` for the YAML template and duplication guard.\n- \"Your support handbook keeps mentioning `core users` without defining it. Is this `users WHERE tier = 'premium'`? Want me to make a view?\"\n- \"The data dictionary says `events.payload` is JSON but the column has no description — let me draft one.\"\n\nFor any aggregation-shaped proposal (`SUM`, `COUNT`, `AVG`, \"by month / by status / per customer\" patterns), **default to a cube**. Run `wren cube list` + `wren cube describe` first to confirm no existing cube already covers the measure expression; if one does, skip the proposal and add a `knowledge/sql/` example pointing at the existing cube instead. The full decision tree, naming rules, and validation flow live in `cube_proposals`.\n\n**In grill mode, open every Lane 3 question with \"I'm guessing — \".** In auto-pilot, tag the audit entry with `agent inference` so the user sees you extrapolated.\n\n## Step 6 — Resolve gaps\n\nBranch on the `MODE` locked in Step 0.\n\n### Grill mode\n\nUse this conversational pattern for every gap surfaced in Lanes 1–3:\n\n> Interview the user relentlessly about every gap until we reach a shared understanding. Walk down each branch of the decision tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.\n>\n> Ask the questions one at a time.\n>\n> If a question can be answered by exploring the codebase or the raw files, do that instead of asking.\n\nFor every grill turn:\n\n1. State the gap and where it came from (Lane 1 / 2 / 3, and for Lane 2 quote the raw file + a short excerpt).\n2. Propose the concrete answer — draft the description, the rule, the SQL pair, the relationship.\n3. **Propose the sink** (\"I'll add this to `knowledge/rules/` as a rule\" / \"I'll add this to the `users` model description in MDL\").\n4. Let the user accept / edit / skip.\n5. On accept: write back (Step 7).\n6. On edit: apply their wording, then write back.\n7. On skip: drop it, move to the next gap. Do not requeue.\n\nWhen the user gives a curve-ball answer (\"actually we don't track that\") — pivot. The goal is shared understanding, not pushing a pre-built list.\n\n### Auto-pilot mode\n\nProcess every finding from Lanes 1–3 directly — **except** for the three escalation cases from Universal Rule 7:\n\n- **Lane 2 conflict** — drop into grill for this single question, never auto-resolve.\n- **Lane 3 proposing a new metric / view / relationship** — drop into grill before applying.\n- **Lane 2 routing ambiguous** — drop into grill for the sink choice only, then auto-apply.\n\nFor everything else (Lane 1 mechanical fixes, Lane 2 unambiguous new claims, Lane 3 low-impact description tweaks):\n\n1. Synthesize the concrete proposal (description / rule / SQL pair) using your best inference.\n2. Decide the sink using the routing table (Step 7).\n3. Write back.\n4. Run `wren context validate` immediately after any MDL edit. On failure: revert the single change and log the revert with the error.\n5. Append to the audit list: the finding, the sink, confidence tag (high / med / low), and source (raw file + excerpt for Lane 2, `agent inference` for Lane 3, `structural` for Lane 1).\n\nAuto-pilot does not pause for confirmation on each item — the user reviews the full diff + audit list in Step 9. They are the reviewer, not the gatekeeper.\n\n## Step 7 — Routing & writeback\n\nDecide the sink as part of the proposal (Step 6.3 in grill mode; Step 6.2 in auto-pilot), so the user can correct routing in grill mode and audit it in auto-pilot.\n\n| Finding type | Sink | How to write |\n|---|---|---|\n| Schema structure / relationship / view / model or column description | **MDL YAML** under `models/`, `views/`, `relationships.yml` | Edit the YAML file directly. For catalog #1 / #2 / #3 / #5 / #7 / PII, append a `[tag]` line to `properties.description` (prose first, then one tag per category). See `gap_catalog` for the exact tag format and triggers. |\n| Aggregation metric / named measure (with measures + dimensions) | **`cubes/<name>/metadata.yml`** | New file per cube. Default sink for any `SUM` / `COUNT` / `AVG` / ratio metric raw defines or Lane 3 infers. See `cube_proposals` for the YAML template, naming policy, duplication guard, and validation flow. Run `wren context validate` + `wren cube query --cube <name> --sql-only` after writing; revert on either failure. **Always escalates to grill in auto-pilot** (Universal Rule 7b). |\n| Default filter / implicit rule / business convention / naming convention / external mapping / currency / canonical table | **`knowledge/rules/`** | Append under the catalog-specified `##` section heading (#4 → `## Default filters`, #6 → `## Naming conventions`, #8 → `## External identifiers`, #9 → `## Currency`, #10 → `## Canonical tables`) inside a topic file under `knowledge/rules/` (e.g. `knowledge/rules/conventions.md`). Create the file/heading if absent; never modify existing text. |\n| NL→SQL example (canonical or ad-hoc) | **`knowledge/sql/`** | `wren memory store --nl \"...\" --sql \"...\" --tags \"source:enrich\"` — writes `knowledge/sql/<slug>.md` (committable) and indexes it when the extra is present. Works whether or not `MEMORY_AVAILABLE`. |\n\nCatalog-driven routing means every column-local proposal goes to the column's `properties.description` with a `[tag]` line; every cross-model rule goes to `knowledge/rules/` under a fixed heading. This keeps re-enrichment deterministic (greppable) and avoids inventing new sink locations.\n\n### After every MDL edit\n\n```bash\nwren context validate\n```\n\nIf it fails:\n1. Revert the single change you just made.\n2. Show the user the validation error (grill mode) or log it in the audit (auto-pilot).\n3. In grill mode, re-grill on that specific gap with the error as new context.\n4. In auto-pilot, mark the finding as \"revert: validation failed\" and move on.\n\n### Format reminders\n\n- NL→SQL pairs are written by `wren memory store`; each becomes a `knowledge/sql/<slug>.md` with YAML frontmatter (`nl`, `sql`, `source`, optional `datasource`/`tags`). Don't hand-write the files — use `wren memory store --tags \"source:enrich\"`.\n- MDL YAML uses snake_case keys (e.g. `primary_key`, `is_calculated`, `not_null`). `wren context build` converts to camelCase for `target/mdl.json`.\n- `knowledge/rules/` holds free-form markdown, one file per topic. Group rules by topic with `##` headings.\n\n## Step 8 — Session finalize\n\nAfter Step 6 ends (user says stop in grill mode, or every finding is processed in auto-pilot):\n\n```bash\nwren context build\n```\n\nThis recompiles `target/mdl.json` from the YAML edits.\n\nIf `MEMORY_AVAILABLE = true`:\n\n```bash\nwren memory index\n```\n\nThis rebuilds the index from the MDL schema items and the `knowledge/sql/` pairs. (Business\nrules in `knowledge/rules/` are read by `wren context instructions`, not embedded by `index`.)\n\n## Step 9 — Summary\n\n### Both modes — common section\n\nPrint a tight session report:\n\n```text\nWren Enrich Context — session summary  (mode: <grill|autopilot>)\n\nAdded:\n  MDL              : N model descriptions, N column descriptions, N relationships, N views\n                     by tag: [enum]=N [unit]=N [null]=N [magic]=N [time]=N [pii]=N\n  cubes            : N new (names: <list>)                                    via cubes/<name>/metadata.yml\n  knowledge/rules/ : N new rules across sections\n                     by section: Default filters=N | Naming conventions=N | External identifiers=N | Currency=N | Canonical tables=N\n  knowledge/sql/   : N new NL→SQL pairs                                       via wren memory store\n  Probe            : N columns sampled, M failed                             (grill mode only)\n\nPlease fix manually (we don't edit existing fields):\n  - models/orders/metadata.yml: existing description seems to contradict raw/glossary.pdf p.3\n  - relationships.yml: existing orders↔customers is MANY_TO_ONE but raw/data_dict.md p.7 says MANY_TO_MANY\n  - …\n```\n\n### Grill mode extras\n\nAppend:\n\n```text\nSkipped this session: N gaps (re-run /wren-enrich-context to revisit)\n```\n\n### Auto-pilot mode extras\n\nAppend a detailed audit so the user can sanity-check inferences:\n\n```text\nInferred items (please review):\n  high   | MDL model:orders.description            | from raw/glossary.pdf p.2 — \"Order = ...\"\n  high   | knowledge/rules/ rule                   | from raw/handbook.md §4 — \"default tier ...\"\n  med    | MDL column:users.signup_source.desc     | agent inference from raw/onboarding.md\n  low    | knowledge/sql/: \"weekly active customers\" | agent inference, no direct raw evidence\n\nValidation:\n  K successful applies, M reverted after wren context validate failed:\n    - relationships.yml: <error> → reverted\n\nEscalated to grill (raw vs MDL conflicts / high-impact additions):\n  - <count> items — see grill transcript above\n```\n\nThe user should be encouraged to skim the audit and either accept it as-is, manually tweak low-confidence rows, or re-run in grill mode if they want to revisit interactively.\n\n## Things to avoid\n\n- Do not write a `gaps.yml`, `state.yml`, or any other tracking artifact. The session lives entirely in conversation.\n- Do not modify any existing MDL field, `knowledge/rules/` rule, or `knowledge/sql/` pair — only append / add. Surface mismatches on the manual-fix list.\n- Do not install new Python packages (`pypdf`, `docling`, …) to read raw. Use what your agent already has; ask the user to convert files you can't open.\n- Do not auto-resolve a conflict between raw and current MDL — always grill the user, in **both** modes.\n- Do not present Lane 3 inferences as if they were quoted from raw. Open with \"I'm guessing — \" (grill) or tag `agent inference` (auto-pilot).\n- `wren memory store` works whether or not `MEMORY_AVAILABLE` — it always writes the `knowledge/sql/*.md` pair (and indexes it only when the extra is present). No need to fall back to another sink.\n- Do not commit anything to git. The user owns the commit decision.\n- Do not nag about skipped questions. Skip is skip for this session (grill mode only — auto-pilot has no skip concept).\n- Do not run `wren context build` after every single MDL edit — once at the end is enough. Do run `wren context validate` after every edit.\n- Do not assume `raw/` was created by `wren context init` — it isn't. This skill creates it.\n- Do not switch modes mid-session. The user re-runs to change mode.\n- Do not append a `[tag]` line if the same category tag already exists for that column — Universal Rule 1. Surface contradictions on the manual-fix list instead.\n- Do not invent new `knowledge/rules/` section headings. Stick to the five catalog-defined headings (`## Default filters`, `## Naming conventions`, `## External identifiers`, `## Currency`, `## Canonical tables`). Anything that doesn't fit goes on the manual-fix list.\n- Do not probe the live DB in auto-pilot mode. Step 4.5 is grill-only by default.\n- Do not propose a cube whose measure expression already exists in another cube on the same `base_object` — write a `knowledge/sql/` example pointing at the existing cube instead. See `cube_proposals` duplication guard.\n- Do not modify an existing cube YAML even when raw contradicts it — Universal Rule 1. Surface on the manual-fix list.\n- Do not write a new cube alongside an old MDL `metrics:` entry that already covers the same logic. Surface as \"consider migrating to cube\" on the manual-fix list.\n- Do not skip `wren cube query --cube <name> --sql-only` after creating a cube. Structural `wren context validate` doesn't catch unresolvable measure / dimension expressions.\n- In auto-pilot, do not auto-apply Lane 2 conflicts or new metric / view / relationship inferences — always drop into grill for those.\n\n## See also\n\n- `gap_catalog` — the ten business-semantic gap categories, with triggers, default sinks, and write formats. Read this before Step 4.5 and Step 5.\n- `cube_proposals` — decision tree for when to propose a cube vs view vs calculated column, the cube YAML template, naming policy, duplication guard, and validation flow. Read this before any Lane 3 aggregation-shaped proposal.\n","core/wren/src/wren/skills_content/genbi/SKILL.md":"---\nname: genbi\ndescription: \"Turn a Wren project's context layer into a shareable, browser-side GenBI web app and deploy it to the user's Vercel or Cloudflare account. Orchestrates the full flow: `wren genbi build` returns a project-hydrated build instruction, the agent authors the app from scratch into apps/<name>/, then register → verify → deploy produce a shareable URL. Use this skill whenever the user wants to: build a dashboard from their Wren project, make a shareable analytics app, deploy their context layer as a web app, host a GenBI app on Vercel or Cloudflare Pages, or asks for a 'genbi app'.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren GenBI App — Agent Workflow Guide\n\n> This guide is served by the `wren` CLI (`wren skills get genbi`), so it\n> always matches your installed wrenai version.\n\nTurn a Wren context layer into a shareable GenBI app — from a natural-language\nrequest to a public URL in one conversation.\n\n**Division of labor:** the CLI owns the authoritative build instruction (it\nknows the live project facts and the pinned `wren-core-wasm` version) and all\ndeterministic state (index, verify, deploy). You — the agent — author the app\ncode by following the instruction. Never hand-write `.wren/apps.yml`.\n\n## Preconditions\n\n1. A Wren project is discoverable (`wren_project.yml` in cwd/ancestors, or ask\n   for the path and pass `-p`).\n2. The context layer exists. If `target/mdl.json` is missing, `wren genbi\n   build` compiles it implicitly — no separate step needed.\n3. `wren` CLI ≥ the version that ships the `genbi` command group\n   (`wren genbi --help` works).\n\n## Workflow\n\n### 1. Resolve the app name and data mode\n\n- App name: short kebab-case derived from the request (e.g. `sales-overview`).\n- Data mode:\n  - `snapshot` (default) — data is bundled with the app as parquet/duckdb and\n    queried client-side. Fully serverless. Right for demos, reports, small\n    data, and dlt-pipeline output.\n  - `live` — the app calls back to the user's warehouse/API at view time.\n    Right for production-scale or always-fresh data. Requires a CORS-enabled\n    endpoint and carries strict no-credentials rules.\n- Ask the user ONLY if the choice is genuinely ambiguous.\n\n### 2. Get the build instruction\n\n```bash\nwren genbi build <name> --prompt \"<the user's request, verbatim>\" --data-mode <mode>\n```\n\nFor long or multi-line prompts use `--prompt-file <file>` or pipe to\n`--prompt -`. The command prints the authoritative build instruction —\nwasm wiring (pinned version, CDN load), the project's model/column inventory,\ndata-mode guidance, acceptance criteria, and the target folder. It writes no\napp files — the only thing it may touch is `target/mdl.json`, which it compiles\nfirst if missing (see Precondition 2).\n\n### 3. Author the app\n\nFollow the instruction exactly. Key conventions:\n\n- Write everything under `apps/<name>/` — never outside it.\n- Copy the compiled MDL into the app as `apps/<name>/mdl.json`.\n- Load `wren-core-wasm` from the CDN given in the instruction; never bundle\n  the ~68MB binary.\n- snapshot: export the data the dashboard needs into `apps/<name>/data/` as\n  parquet (`verify` requires at least one `.parquet`/`.duckdb` asset). See\n  **Snapshot data export** below for the recipe and where the data comes from.\n- live: write an endpoint-only connection config. NEVER inline credentials —\n  `verify` scans for them (best-effort) and `deploy` gates on `verify`, but\n  the rule is on you: a public static host exposes every shipped file.\n- Design the dashboard to actually answer the user's request: pick the right\n  charts/tables for the question, not a generic template.\n\n#### Snapshot data export\n\nThe CLI hands you the build instruction, but the snapshot bytes still have to\nbe exported into the app folder — that step is yours.\n\n**Where the data comes from:**\n\n- **DuckDB-backed project** (incl. anything loaded by the `dlt-connector`\n  skill — its pipelines always land in a `.duckdb` file): the project's DuckDB\n  file *is* your snapshot source. If the user is connecting SaaS data (HubSpot,\n  Stripe, Salesforce, …) and has no project yet, run the SaaS→project flow\n  first: `wren skills get dlt-connector`. Then come back here to ship it.\n- **Warehouse-backed project** (Postgres, BigQuery, Snowflake, …): run the\n  query/queries the dashboard needs through the MDL layer and write the result\n  to parquet. Keep snapshots small — snapshot mode is for demos/reports, not\n  full warehouse extracts; use `live` mode for large or always-fresh data.\n\n**Recipe (DuckDB → parquet):**\n\n```bash\n# from the project root; <db> is the project's DuckDB file (see wren_project.yml)\npython - <<'PY'\nimport duckdb\ncon = duckdb.connect(\"<db>.duckdb\", read_only=True)\ncon.execute(\n    \"COPY (SELECT * FROM <table>) \"\n    \"TO 'apps/<name>/data/<table>.parquet' (FORMAT parquet)\"\n)\nPY\n```\n\nOnly export the columns/rows the dashboard uses. The compiled `mdl.json` you\ncopied in keeps the context layer intact regardless of how you bundle data.\n\n### 4. Register and verify\n\n```bash\nwren genbi register <name> --data-mode <mode>\nwren genbi verify <name>\n```\n\nIf verify fails: fix the reported problems and re-run verify. Do NOT proceed\nto deploy on a failed verify. Offer `wren genbi open <name>` for a local\npreview before shipping.\n\n### 5. Deploy (only if the user asked for it)\n\n```bash\nwren genbi deploy <name> --provider vercel      # or cloudflare\n```\n\n- Preview deployment by default. **Confirm with the user before `--prod`.**\n- Tokens: the CLI discovers `VERCEL_TOKEN` / `CLOUDFLARE_API_TOKEN` from the\n  environment or `.env` files. If missing, ask the user to export it or add\n  it to the project `.env`. NEVER put a token on the command line.\n- Cloudflare also needs `CLOUDFLARE_ACCOUNT_ID` (env or `.env`), a token\n  scoped with Pages:Edit, and the `wrangler` CLI on PATH (or `npx` available)\n  — the adapter shells out to `wrangler pages deploy`. If it's missing, ask\n  the user to `npm install -g wrangler`.\n- Report the returned URL to the user. Re-deploying the same app updates the\n  same provider target.\n- **Verify the URL actually loads.** After deploying, fetch the URL — a\n  successful deploy can still return **HTTP 401/403** to outsiders because of\n  the provider's access protection (see below). Don't report a link as\n  \"shareable\" until you've confirmed it serves.\n\n### Vercel Deployment Protection (the 401 trap)\n\nNew Vercel projects ship with **Vercel Authentication** turned ON by default,\nso every deployment — preview *and* production — returns **401** to anyone not\nlogged into the owning Vercel account/team. The deploy itself succeeded; the\nURL is just gated.\n\n- To make the app publicly shareable, the user disables it in the Vercel\n  dashboard: **Project → Settings → Deployment Protection → Vercel\n  Authentication → Disabled** (or scope it to production only). This setting\n  is not controllable from `wren genbi deploy`; it's a one-time toggle per\n  project in Vercel.\n- If the user only needs a private link (viewable while logged into their\n  Vercel account), leaving protection on is fine — just tell them the link\n  won't work for logged-out visitors.\n\n## Safety boundaries\n\n- Never inline secrets/credentials into app files — the deploy target is a\n  public static host; anyone with the URL can read every file. `verify`\n  scans for inlined credentials, but treat it as best-effort\n  defense-in-depth, not a guarantee — never rely on it to catch a secret\n  you shouldn't have written in the first place.\n- Never pass tokens as CLI flags; they leak into shell history.\n- Confirm before production deploys.\n- All index state goes through `wren genbi register/remove` — never edit\n  `.wren/apps.yml` by hand.\n\n## Quick reference\n\n| Step | Command |\n| --- | --- |\n| Get build instruction | `wren genbi build <name> --prompt \"…\" [--data-mode snapshot\\|live]` |\n| Record the app | `wren genbi register <name> --data-mode <mode>` |\n| Preflight | `wren genbi verify <name>` |\n| Local preview | `wren genbi open <name>` |\n| Ship | `wren genbi deploy <name> --provider vercel\\|cloudflare [--prod]` |\n| Inventory | `wren genbi list` / `wren genbi remove <name>` |\n","core/wren/src/wren/skills_content/generate-mdl/SKILL.md":"---\nname: generate-mdl\ndescription: \"Generate a Wren MDL project by exploring a database with available tools (SQLAlchemy, database drivers, MCP connectors, or raw SQL). Guides agents through schema discovery, type normalization, and MDL YAML generation using the wren CLI. Use when: user wants to create or set up a new MDL, onboard a new data source, or scaffold a project from an existing database.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Generate Wren MDL — CLI Agent Workflow\n\nBuilds an MDL project by discovering database schema and converting it\ninto Wren's YAML project format. The agent uses whatever database tools\nare available in its environment for introspection; the wren CLI handles\ntype normalization, validation, and build.\n\nFor memory and query workflows after setup, run `wren skills get usage`.\n\n## Prerequisites\n\n- `wren` CLI installed (`pip install \"wrenai[<datasource>]\"`)\n- A working database connection (credentials available to the agent)\n- A connection profile (set up via `wren profile add`) or connection info ready\n\n## Alternative — import from an OpenMetadata catalog\n\nIf the user's team already catalogs this database in OpenMetadata, import\nfrom the catalog instead of introspecting the live database. The import\ncarries curated table/column descriptions, tags, owners, and foreign-key\nconstraints (as `relationships.yml`), so the join-inference work in Phase 4\nis mostly done:\n\n```bash\nwren context import openmetadata --host <om_url> --token <om_token> \\\n  --service <om_service> --database <db> --schema <schema> --dry-run\nwren context import openmetadata --host <om_url> --token <om_token> \\\n  --service <om_service> --database <db> --schema <schema>\n```\n\nAsk the user for the OpenMetadata URL, a read-only bot or personal access\ntoken, and the service name as it appears in OpenMetadata. `--table`\n(repeatable) narrows the import. Without `--force` the command only adds\nfiles that do not exist and lists the collisions it left untouched; `--force`\nregenerates from OpenMetadata and discards local edits to generated files —\nconfirm with the user before using it. Relationships come from foreign keys\nonly; review whether additional joins are needed (Phase 4), then bind a\nprofile and finish with Phase 5 (`wren context validate` → `wren context build`).\n\n## Phase 0 — Detect existing project\n\n**Goal:** If the current directory is already inside a wren project, let the user decide how to proceed.\n\nCheck whether `wren_project.yml` exists in the current working directory\n(or any parent up to the repository root). If found:\n\n1. Tell the user that an existing wren project was detected and show its path.\n2. Ask:\n   - **Reset** — wipe the existing project (`models/`, `views/`,\n     `relationships.yml`, `knowledge/`, and rebuild `wren_project.yml`)\n     and regenerate from scratch in the same directory.\n   - **New path** — keep the existing project untouched and choose a\n     different directory for the new project. Ask the user for the new path,\n     then `wren context init --path <new_path>` and continue from Phase 1\n     using that path.\n\nIf no existing project is detected, proceed directly to Phase 1.\n\n## Phase 1 — Establish connection and scope\n\n**Goal:** Confirm the agent can reach the database and agree on scope with the user.\n\n1. Verify connectivity using whichever tool is available:\n   - If SQLAlchemy: `engine.connect()` test\n   - If database driver: simple query like `SELECT 1`\n   - If wren profile exists: `wren profile debug` to check config\n   - If raw SQL via wren: `wren --sql \"SELECT 1\"` (requires profile or connection file)\n\n2. Ask the user:\n   - Which **schema(s)** or **dataset(s)** to include (skip if only one exists)\n   - Whether to include **all tables** or a subset\n   - The **datasource type** for wren (e.g., `postgres` (including Aurora), `mysql` (including Aurora), `bigquery`, `snowflake`) — needed for type normalization dialect\n\n## Phase 2 — Discover schema\n\n**Goal:** Collect table names, column names, column types, and constraints.\n\nUse whatever introspection method is available. Here are common approaches\nranked by convenience:\n\n### Option A: SQLAlchemy (recommended if available)\n\n```python\nfrom sqlalchemy import create_engine, inspect\n\nengine = create_engine(connection_url)\ninspector = inspect(engine)\n\ntables = inspector.get_table_names(schema=\"public\")\n\nfor table in tables:\n    columns = inspector.get_columns(table, schema=\"public\")\n    # columns → [{\"name\": \"id\", \"type\": INTEGER(), \"nullable\": False, ...}]\n\n    pk = inspector.get_pk_constraint(table, schema=\"public\")\n    # pk → {\"constrained_columns\": [\"id\"], \"name\": \"orders_pkey\"}\n\n    fks = inspector.get_foreign_keys(table, schema=\"public\")\n    # fks → [{\"constrained_columns\": [\"customer_id\"],\n    #          \"referred_table\": \"customers\",\n    #          \"referred_columns\": [\"id\"]}]\n```\n\n### Option B: Database-specific driver\n\n- **psycopg / asyncpg (Postgres):** Query `information_schema.columns` and `information_schema.table_constraints`\n- **google-cloud-bigquery:** `client.list_tables()`, `client.get_table()` → `table.schema`\n- **snowflake-connector-python:** `SHOW COLUMNS IN TABLE`, `SHOW PRIMARY KEYS IN TABLE`\n- **clickhouse-driver:** `DESCRIBE TABLE`, `system.tables`\n\n### Option C: Raw SQL via wren\n\nIf no driver is available but a wren profile is configured, query\n`information_schema` through wren itself:\n\n```bash\nwren --sql \"SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'\" -o json\nwren --sql \"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'orders'\" -o json\n```\n\nNote: this goes through the MDL layer, so it only works if you already\nhave a minimal MDL or if the database supports `information_schema` as\nregular tables. For bootstrapping from zero, Option A or B is preferred.\n\n## Phase 3 — Normalize types\n\n**Goal:** Convert raw database types to wren-core-compatible types.\n\n### Python import (recommended for batch processing)\n\n```python\nfrom wren.type_mapping import parse_type, parse_types\n\n# Single type\nnormalized = parse_type(\"character varying(255)\", \"postgres\")  # → \"VARCHAR(255)\"\n\n# Batch — entire table at once\ncolumns = [\n    {\"column\": \"id\", \"raw_type\": \"int8\"},\n    {\"column\": \"name\", \"raw_type\": \"character varying\"},\n    {\"column\": \"total\", \"raw_type\": \"numeric(10,2)\"},\n]\nnormalized_cols = parse_types(columns, dialect=\"postgres\")\n# Each dict now has a \"type\" key with the normalized value\n```\n\n### CLI (if Python import not available)\n\nSingle type:\n```bash\nwren utils parse-type --type \"character varying(255)\" --dialect postgres\n# → VARCHAR(255)\n```\n\nBatch (stdin JSON):\n```bash\necho '[{\"column\":\"id\",\"raw_type\":\"int8\"},{\"column\":\"name\",\"raw_type\":\"character varying\"}]' \\\n  | wren utils parse-types --dialect postgres\n```\n\n## Phase 4 — Scaffold and write MDL project\n\n**Goal:** Create the YAML project structure.\n\n### Step 1 — Initialize project\n\n```bash\nwren context init --path /path/to/project\n```\n\nThis creates:\n```text\nproject/\n├── wren_project.yml\n├── models/              # business-facing tables/models\n├── views/               # named SQL statements\n├── cubes/               # pre-aggregation cubes (measures + dimensions)\n├── relationships.yml\n└── knowledge/           # business rules (rules/) + NL→SQL pairs (sql/)\n```\n\n> **When to define cubes:** If the user asks aggregation questions like\n> \"revenue by month\" or \"top customers\", define cubes alongside models —\n> they give agents a structured query API instead of forcing them to\n> hand-write `GROUP BY` / `DATE_TRUNC` SQL. See the\n> [Cube guide](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/cubes.md).\n\n> **IMPORTANT: `catalog` and `schema` in `wren_project.yml`**\n>\n> These are Wren Engine's internal namespace — they are NOT the database's\n> native catalog or schema. Keep the defaults (`catalog: wren`, `schema: public`)\n> unless you are intentionally configuring a multi-project namespace.\n>\n> Your database's actual catalog/schema is specified per-model in `table_reference`\n> (see Step 2). Do not copy database catalog/schema values into `wren_project.yml`.\n\n### Step 2 — Write model files\n\nFor each table, create a YAML file under `models/`. Use snake_case\nnaming (the build step converts to camelCase automatically).\n\n```yaml\n# models/orders/metadata.yml\nname: orders\ntable_reference:\n  catalog: \"\"           # database catalog (empty string if not applicable;\n                        #   for DuckDB, use the DB file name without extension,\n                        #   e.g. jaffle_shop.duckdb → catalog: jaffle_shop)\n  schema: public        # database schema (this IS the DB schema)\n  table: orders         # database table name\nprimary_key: order_id\ncolumns:\n  - name: order_id\n    type: INTEGER\n    not_null: true\n  - name: customer_id\n    type: INTEGER\n  - name: total\n    type: \"DECIMAL(10, 2)\"\n  - name: status\n    type: VARCHAR\n    properties:\n      description: \"Order status: pending, shipped, delivered, cancelled\"\n```\n\n### Step 3 — Write relationships\n\nFrom foreign key constraints discovered in Phase 2:\n\n```yaml\n# relationships.yml\nrelationships:\n  - name: orders_customers\n    models:\n      - orders\n      - customers\n    join_type: many_to_one\n    condition: \"orders.customer_id = customers.customer_id\"\n```\n\nJoin type mapping:\n- FK table → PK table: `many_to_one`\n- PK table → FK table: `one_to_many`\n- Unique FK: `one_to_one`\n- Junction table: `many_to_many`\n\nIf no foreign keys were found, infer from naming conventions:\n- Column `<table>_id` or `<table_singular>_id` → likely FK to `<table>`\n- Ask the user to confirm inferred relationships\n\n### Step 4 — Add descriptions (optional but valuable)\n\nAsk the user to describe:\n- Each model (1-2 sentences about what the table represents)\n- Key columns (especially calculated fields or non-obvious names)\n\nThese descriptions are indexed by `wren memory index` and significantly\nimprove LLM query accuracy.\n\n## Phase 5 — Validate and build\n\n```bash\n# Validate YAML structure and integrity\nwren context validate --path /path/to/project\n\n# If strict mode is desired:\nwren context validate --path /path/to/project --strict\n\n# Build JSON manifest\nwren context build --path /path/to/project\n\n# Verify against database\nwren --sql \"SELECT * FROM <model_name> LIMIT 1\"\n```\n\nIf validation fails, fix the reported issues and re-run. Common errors:\n- Duplicate model/column names\n- Missing primary key\n- Relationship referencing non-existent model\n- Invalid column type (try re-running through `parse_type`)\n\n## Phase 6 — Initialize memory\n\n```bash\n# Index schema (generates seed NL-SQL examples automatically)\nwren memory index\n\n# Verify\nwren memory status\n```\n\nAfter this step, `wren memory fetch` and `wren memory recall` are\noperational. See `wren skills get usage` for query workflows.\n\n## Phase 7 — Iterate with the user\n\nThe initial MDL is a starting point. Improve it by:\n- Adding calculated columns based on business logic\n- Adding views for common query patterns\n- Refining descriptions based on actual query usage\n- Adding access control (RLAC/CLAC) if needed\n\nEach change follows: edit YAML → `wren context validate` →\n`wren context build` → `wren memory index`.\n\n## Quick reference\n\n| Task | Command / Method |\n|------|-----------------|\n| Discover tables | Agent's own tools (SQLAlchemy, driver, raw SQL) |\n| Discover columns + types | Agent's own tools |\n| Discover constraints | Agent's own tools |\n| Normalize types (Python) | `from wren.type_mapping import parse_type` |\n| Normalize types (CLI) | `wren utils parse-type --type T --dialect D` |\n| Normalize types (batch) | `wren utils parse-types --dialect D < columns.json` |\n| Scaffold project | `wren context init` |\n| Write models | Create `models/<name>/metadata.yml` |\n| Write relationships | Edit `relationships.yml` |\n| Validate | `wren context validate` |\n| Build manifest | `wren context build` |\n| Test query | `wren --sql \"SELECT * FROM <model> LIMIT 1\"` |\n| Index memory | `wren memory index` |\n\n## Things to avoid\n\n- Do not hardcode database-specific type strings in MDL — always normalize via `parse_type`\n- Do not skip validation before build — invalid YAML produces broken manifests silently\n- Do not guess column types — introspect from the actual database\n- Do not write relationships without confirming join conditions — wrong conditions cause silent query errors\n- Do not skip `wren memory index` after build — stale indexes degrade recall quality\n","core/wren/src/wren/skills_content/onboarding/SKILL.md":"---\nname: onboarding\ndescription: \"Onboard a user to Wren Engine end-to-end. Walks through environment checks, project scaffolding, connection configuration via .env, and first query. Use when: user wants to install Wren Engine, set up a new data source connection, or bootstrap a new project from scratch. Triggers: '/wren-onboarding', 'install wren', 'set up wren engine', 'wren onboarding', 'connect new database to wren'.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren Onboarding — Agent Workflow\n\nThis skill walks the agent through onboarding — environment checks, project scaffolding, profile creation, MDL generation, and first query. **Procedural details, per-datasource setup notes, and the troubleshooting playbook live in the docs**, not here. The skill's job is to enforce the agent-side rules (one step per turn, never ask for credentials in chat) and to dispatch the agent to the right doc / sibling skill at each step.\n\nReference docs (the skill points to these — never duplicate their content):\n- [`docs/core/get_started/installation.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/get_started/installation.md) — CLI install + skill install\n- [`docs/core/guides/connect.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md) — full connection procedure, **per-datasource setup notes, complete troubleshooting playbook**\n- [`docs/core/get_started/quickstart.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/get_started/quickstart.md) — bundled `jaffle_shop` demo\n\n## Mode of operation — READ THIS FIRST\n\n**One step per round-trip.** Each numbered step below is its own turn: explain briefly, ask **only** what the step needs, run the command(s), confirm, move on.\n\n- ❌ **Never collect information for future steps upfront.** Do not ask for project name + database type + credentials in one message.\n- ❌ **Never ask for credentials in chat — not host, port, user, password, tokens, anything.** Credentials always go through `.env`. The user fills the file in their editor; the agent never sees the values.\n- ❌ **Never query the database before MDL is built** via `wren skills get generate-mdl`.\n- ❌ **Never invent connection field names.** Always run `wren docs connection-info <ds>` to see the real fields — it's introspected from the live Pydantic schema, so it's always correct.\n- ✅ Wait for each command to finish, report its output in plain language, then move on.\n- ✅ For any error, consult `connect.md#troubleshooting` and surface the relevant section to the user — don't carry a copy of the playbook here.\n\n## Preflight (environment only — no user questions about the project)\n\nRead-only checks. Report findings, do **not** ask about project / credentials / datasource yet.\n\n1. `python3 --version` — requires Python 3.11+. If older, ask the user to upgrade and stop.\n2. Check virtualenv: `python3 -c \"import sys; print(sys.prefix != sys.base_prefix)\"`. If `False`, offer to create one (`python3 -m venv .venv && source .venv/bin/activate`). PEP 668 systems will need this.\n3. `wren --version` — if already installed, confirm before reinstalling.\n4. `pwd` — record it. Don't ask where the project should live yet.\n\nReport findings as a 4-bullet list, then continue.\n\n## Early branch — demo or own database?\n\n> \"Try the bundled `jaffle_shop` demo first (~30s, no DB needed), or connect your own database?\"\n\n- **demo** → point at [`quickstart.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/get_started/quickstart.md) and stop this skill.\n- **own DB** → continue.\n\n## Step 1 — Collect project name + database type\n\nThese two are the only thing Step 2 needs; ask both together so the user has a clean handoff:\n\n> \"Two things before I scaffold:\n> 1. **Project name** — I'll create `~/<name>/` and `cd` into it.\n> 2. **Database type** — run `wren docs connection-info` (no argument) to see the full list, or pick a common one: `postgres` (use for Aurora PostgreSQL), `mysql` (use for Aurora MySQL), `bigquery`, `snowflake`, `clickhouse`, `trino`, `duckdb`, …\"\n\nWait for both. Don't ask for credentials.\n\n## Step 2 — Workspace + .env setup (batch)\n\nSide effects: creates `~/<project>/`, installs `wrenai[<ds>,main]`, generates an empty `.env` template. The project files (`wren_project.yml` etc.) come later in Step 3.5 — at this point we only have a directory with credentials waiting to be filled.\n\nRun as a batch — report each command briefly, then end with one \"please fill `.env`\" ask:\n\n1. `mkdir -p ~/<project> && cd ~/<project>`.\n2. `pip install \"wrenai[<ds>,main]\"`. For datasource-specific install gotchas (macOS mysql, etc.), see [`connect.md#per-datasource-setup-notes`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md).\n3. **Generate the `.env` template by introspecting the connector**:\n\n   ```bash\n   wren docs connection-info <ds> --format md\n   ```\n\n   Use the field list to write `.env` with `<DS>_<FIELD>=` keys (UPPER_SNAKE), values **empty**. Example for postgres:\n\n   ```ini\n   POSTGRES_HOST=\n   POSTGRES_PORT=5432\n   POSTGRES_DATABASE=\n   POSTGRES_USER=\n   POSTGRES_PASSWORD=\n   ```\n\n   Special encodings (BigQuery base64, Snowflake account format, Athena AWS creds, etc.) are documented in [`connect.md#per-datasource-setup-notes`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md). Surface the relevant section to the user verbatim — don't paraphrase.\n\n4. Add `.env` to `.gitignore` if the project is a git repo. Suggest `chmod 600 .env`.\n5. Tell the user: `.env` is at `<path>`, please fill every value and reply **\"done\"**.\n\n## Step 3 — Create the connection profile\n\nOnly after the user replies \"done\".\n\nWrite `/tmp/conn.yml` with **every field as a `${VAR}` placeholder** matching the `.env` keys you generated in Step 2:\n\n```yaml\ndatasource: <ds>\nhost: ${<DS>_HOST}\nport: ${<DS>_PORT}\n# … one line per field from `wren docs connection-info <ds>`\n```\n\nThen:\n\n```bash\nwren profile add <project> --from-file /tmp/conn.yml\n```\n\nValidation runs automatically. The CLI overwrites profiles silently — there is no `--force` flag.\n\n- ✓ **Success** → continue to Step 3.5.\n- ⚠ **Any warning** → consult [`connect.md#troubleshooting`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md) for the exact symptom (missing secret, driver auth failure, ValidationError, unreachable host, …) and tell the user what to fix.\n\n## Step 3.5 — Scaffold the project\n\n```bash\nwren context init --empty\n```\n\nRefuses to overwrite an existing `wren_project.yml`. Creates the project directory layout (`models/`, `views/`, `relationships.yml`, `knowledge/` (rules + sql), `AGENTS.md`).\n\n## Step 3.6 — Bind the profile to the project\n\n```bash\nwren context set-profile <project>\n```\n\nWrites both `profile: <project>` and `data_source: <ds>` into `wren_project.yml` (data_source is taken from the profile we just validated, so it's guaranteed correct). Future CLI commands and the SDK resolve the connection deterministically — independent of which profile is globally active.\n\nThis step also future-proofs the project for multi-project setups: once the binding is recorded, switching `wren profile switch` elsewhere never breaks this project's queries.\n\n## Step 4 — Generate MDL (hand off)\n\n> ⚠️ The agent **must** build MDL before any data query. Queries against tables not in MDL will fail.\n\nRun `wren skills get generate-mdl` and follow it. It walks the agent through table introspection, type normalization, and YAML generation. When it finishes, return here and run:\n\n```bash\nwren context validate\nwren context build\n```\n\nReport the model count and any validate warnings.\n\n**Memory recommendation**: count models with `wren context show | grep -c '^model:'`. If `>= 200`, suggest `pip install \"wrenai[memory]\"` + `wren memory index` (~800 MB). If `< 200`, skip.\n\n## Step 5 — Ready to explore (hand off)\n\nSuggest 2–3 NL questions based on the discovered tables (e.g. for an orders schema: \"How many orders last month?\", \"Top 5 customers by total\"). Then end this skill: for day-to-day querying the agent should run `wren skills get usage`.\n\n## Cross-skill routing\n\n| Trigger | Skill |\n|---------|-------|\n| User mentions a SaaS source (HubSpot, Stripe, Salesforce, GitHub, Slack, …) | `wren skills get dlt-connector` |\n| User has a connected DB but no MDL yet | `wren skills get generate-mdl` |\n| User has MDL ready, wants to query | `wren skills get usage` |\n| Anything else from-scratch | `wren skills get onboarding` (this skill) |\n\n## On error\n\nDon't carry an error playbook here — surface [`connect.md#troubleshooting`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md) sections to the user. The doc covers:\n\n- `wren: command not found`\n- `pip install … externally-managed-environment`\n- Missing secret (`MissingSecretError`)\n- Driver authentication failures\n- Pydantic `ValidationError` / unknown datasource\n- Connection refused / firewall / cloud DB IP allow-list\n- `wren context validate` warning categories\n\nIf you hit something not in the playbook, tell the user:\n\n> \"I hit an error I don't know how to fix: `<error>`.\n> See <https://docs.getwren.ai/oss/introduction> or open an issue at <https://github.com/Canner/WrenAI/issues>.\"\n","core/wren/src/wren/skills_content/usage/SKILL.md":"---\nname: usage\ndescription: \"Wren Engine CLI workflow guide for AI agents. Answer data questions end-to-end using the wren CLI: gather schema context, recall past queries, write SQL through the MDL semantic layer, execute, and learn from confirmed results. Use when: user asks a data question, requests a report or analysis, asks about metrics, revenue, customers, orders, trends, or any business data; user says 'how many', 'show me', 'what is the', 'top N', 'compare', 'trend', 'growth', 'breakdown'; user wants to explore, analyze, filter, aggregate, or summarize data from a database; agent needs to query data, connect a data source, handle errors, or manage MDL changes via the wren CLI.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren Engine CLI — Agent Workflow Guide\n\n> This guide is served by the `wren` CLI (`wren skills get usage`), so it always matches your installed wrenai version. Pull the deeper reference docs with `wren skills get usage --full`.\n\n## Preflight — Verify environment and installation\n\n**Goal:** Ensure the `wren` CLI is available before entering any workflow.\n\n### Step 1 — Check Python virtual environment\n\nRun `python -c \"import sys; print(sys.prefix)\"` (or equivalent) to determine\nwhether a virtual environment is active.\n\n- If **no venv is active**, warn the user and ask whether to:\n  - Create one (e.g., `python -m venv .venv && source .venv/bin/activate`)\n  - Continue without a venv (not recommended — may pollute global packages)\n\n### Step 2 — Check if the `wren` CLI is installed\n\nRun `wren --version`. If the command is not found or errors:\n\n1. Tell the user that the `wren` CLI is not installed.\n2. Ask if you should help install it.\n3. If the user agrees, determine the **datasource extra** to install:\n\n   **Auto-detect from project:** Check whether the current directory is inside\n   a wren project (look for `wren_project.yml` up to the repository root).\n   If found, read the active profile with `cat ~/.wren/profiles.yml` or look\n   for a datasource hint in the project's profile configuration. Extract the\n   datasource type from there.\n\n   **Ask the user:** If no project is detected or no datasource can be\n   inferred, ask the user which database they plan to connect to. Valid\n   extras: `postgres` (for Aurora Postgres), `mysql` (for Aurora MySQL), `bigquery`, `snowflake`, `clickhouse`,\n   `trino`, `mssql`, `databricks`, `redshift`, `spark`, `athena`, `oracle`.\n   DuckDB is included by default — no extra needed.\n\n4. Install with the detected or chosen extra:\n   ```bash\n   # DuckDB (no extra needed)\n   pip install \"wrenai\"\n\n   # Other datasources\n   pip install \"wrenai[<datasource>]\"\n   ```\n   To also enable semantic memory, interactive prompts, and web UI (recommended):\n   ```bash\n   pip install \"wrenai[<datasource>,main]\"\n   # or for DuckDB:\n   pip install \"wrenai[main]\"\n   ```\n\n5. Verify: `wren --version`\n\nIf `wren --version` succeeds, proceed to the relevant workflow below.\n\n---\n\nThe `wren` CLI queries databases through an MDL (Model Definition Language) semantic layer. You write SQL against model names, not raw tables. The engine translates to the target dialect.\n\nTwo things drive everything:\n- **Profile** — database connection + datasource type, managed via `wren profile` (stored in `~/.wren/profiles.yml`)\n- **Project** — MDL model definitions in YAML, compiled to `target/mdl.json` via `wren context build`\n\nThe CLI reads the active profile for connection info and datasource. Use `wren profile list` to see which profile is active, `wren profile switch <name>` to change it. `dry-plan` also accepts `--datasource` / `-d` for transpile-only use without a profile.\n\nFor memory-specific decisions, see the `memory` reference (run `wren skills get usage --full`).\nFor SQL syntax, CTE-based modeling, and error diagnosis, see the `wren-sql` reference (run `wren skills get usage --full`).\nFor project structure, MDL field definitions, and CLI workflow details, see the [documentation](https://github.com/Canner/WrenAI/tree/main/docs/core).\n\n---\n\n## Workflow 1: Answering a data question\n\n### Step 1 — Gather context\n\n| Situation | Command |\n|-----------|---------|\n| Default | `wren memory fetch -q \"<question>\"` |\n| Need specific model's columns | `wren memory fetch -q \"...\" --model <name> --threshold 0` |\n| Memory not installed | Read `target/mdl.json` in the project directory, or run `wren context show` |\n\nIf this is the first query in the conversation, also run:\n\n```text\nwren context instructions\n```\n\nIf it returns content, treat it as **rules that override defaults** — apply them to all subsequent queries in this session.\n\n### Step 2 — Recall past queries\n\n```bash\nwren memory recall -q \"<question>\" --limit 3\n```\n\nUse results as few-shot examples. Skip if empty.\n\n### Step 2.5 — Assess complexity (before writing SQL)\n\nIf the question involves **any** of the following, consider decomposing:\n- Multiple metrics or aggregations (e.g., \"churn rate AND expansion revenue\")\n- Multi-step calculations (e.g., \"month-over-month growth rate\")\n- Comparisons across segments (e.g., \"by plan tier, by region\")\n- Time-series analysis requiring baseline + change (e.g., \"retention curve\")\n\n**Decomposition strategy:**\n1. Identify the sub-questions (e.g., \"total subscribers at start\" + \"subscribers who cancelled\" → churn rate)\n2. For each sub-question:\n   - `wren memory recall -q \"<sub-question>\"` — check if a similar pattern exists\n   - Write and execute a simple SQL\n   - Note the result\n3. Combine sub-results to answer the original question\n\n**When NOT to decompose:**\n- Single-table aggregation with GROUP BY — just write the SQL\n- Simple JOINs that the MDL relationships already define\n- Questions where `memory recall` returns a near-exact match\n\nThis is a judgment call, not a rigid rule. If you're confident in a single\nquery, go ahead. Decompose when the SQL would be hard to debug if it fails.\n\n### Step 3 — Write, verify, and execute SQL\n\n**For simple queries** (single table or simple MDL-defined JOINs, straightforward aggregation):\nExecute directly:\n```bash\nwren --sql 'SELECT c_name, SUM(o_totalprice) FROM orders\nJOIN customer ON orders.o_custkey = customer.c_custkey\nGROUP BY 1 ORDER BY 2 DESC LIMIT 5'\n```\n\n**For complex queries** (non-trivial JOINs not covered by MDL relationships, subqueries, multi-step logic):\nVerify first with dry-plan:\n```bash\nwren dry-plan --sql 'SELECT ...'\n```\n\nCheck the expanded SQL output:\n- Are the correct models and columns referenced?\n- Do the JOINs match expected relationships?\n- Are CTEs expanded correctly?\n\nIf the expanded SQL looks wrong, fix before executing.\nIf it looks correct, proceed:\n```bash\nwren --sql 'SELECT ...'\n```\n\n**SQL rules:**\n- Target MDL model names, not database tables\n- Write dialect-neutral SQL — the engine translates\n\n### Step 4 — Store and continue\n\nAfter successful execution, **store the query by default**:\n\n```bash\nwren memory store --nl \"<user's original question>\" --sql \"<the SQL>\"\n```\n\n**Skip storing only when:**\n- The query failed or returned an error\n- The user said the result is wrong\n- The query is exploratory (`SELECT * ... LIMIT N` without analytical clauses)\n- There is no natural language question — just raw SQL\n- The user explicitly asked not to store\n\nThe CLI auto-detects exploratory queries — if you see no store hint\nafter execution, the query was classified as exploratory.\n\n| Outcome | Action |\n|---------|--------|\n| User confirms correct | Store |\n| User continues with follow-up | Store, then handle follow-up |\n| User says nothing (but question had clear NL description) | Store |\n| User says wrong | Do NOT store — fix the SQL |\n| Query error | See Error recovery below |\n\n---\n\n## Workflow 2: Error recovery\n\n### \"table not found\"\n\n1. Verify model name: `wren memory fetch -q \"<name>\" --type model --threshold 0`\n2. Check MDL exists: `ls target/mdl.json` (or `wren context show`)\n3. Verify column: `wren memory fetch -q \"<column>\" --model <name> --threshold 0`\n\n### Connection error\n\n1. Check active profile: `wren profile debug`\n2. Verify datasource and connection fields are correct\n3. Test: `wren --sql \"SELECT 1\"`\n4. Valid datasource values: `postgres` (for Aurora Postgres), `mysql` (for Aurora MySQL), `bigquery`, `snowflake`, `clickhouse`, `trino`, `mssql`, `databricks`, `redshift`, `spark`, `athena`, `oracle`, `duckdb`\n5. If no profile exists, create one: `wren profile add --ui` (or `--interactive` / `--from-file`)\n\n### SQL syntax / planning error (enhanced)\n\n#### Layer 1: Identify the failure point\n\n```bash\nwren dry-plan --sql \"<failed SQL>\"\n```\n\n| dry-plan result | Failure layer | Next step |\n|-----------------|---------------|-----------|\n| dry-plan fails | MDL / semantic | → Layer 2A |\n| dry-plan succeeds, execution fails | DB / dialect | → Layer 2B |\n\n#### Layer 2A: MDL-level diagnosis (dry-plan failed)\n\nThe dry-plan error message tells you exactly what's wrong:\n\n| Error pattern | Diagnosis | Fix |\n|---------------|-----------|-----|\n| `column 'X' not found in model 'Y'` | Wrong column name | `wren memory fetch -q \"X\" --model Y --threshold 0` to find correct name |\n| `model 'X' not found` | Wrong model name | `wren memory fetch -q \"X\" --type model --threshold 0` |\n| `ambiguous column 'X'` | Column exists in multiple models | Qualify with model name: `ModelName.column` |\n| Planning error with JOIN | Relationship not defined in MDL | Check available relationships in context |\n\n**Key principle**: Fix ONE issue at a time. Re-run dry-plan after each fix\nto see if new errors surface.\n\n#### Layer 2B: DB-level diagnosis (dry-plan OK, execution failed)\n\nThe DB error + dry-plan output together pinpoint the issue:\n\n1. Read the dry-plan expanded SQL — this is what actually runs on the DB\n2. Compare with the DB error message:\n\n| Error pattern | Diagnosis | Fix |\n|---------------|-----------|-----|\n| Type mismatch | Column type differs from assumed | Check column type in context, add explicit CAST |\n| Function not supported | Dialect-specific function | Use dialect-neutral alternative |\n| Permission denied | Table/schema access | Check connection credentials |\n| Timeout | Query too expensive | Simplify: reduce JOINs, add filters, LIMIT |\n\n**For small models**: If the error message is unclear, try simplifying\nthe query to the smallest failing fragment. Execute subqueries independently\nto isolate which part fails.\n\nFor the CTE rewrite pipeline and additional error patterns, see the `wren-sql` reference (run `wren skills get usage --full`).\n\n---\n\n## Workflow 3: Connecting a new data source\n\n1. Add a profile: `wren profile add --ui` (or `--interactive` / `--from-file`)\n2. Test connection: `wren profile debug`\n3. Test query: `wren --sql \"SELECT 1\"`\n4. Initialize project: `wren context init`\n5. Build manifest: `wren context build`\n6. Index: `wren memory index`\n7. Verify: `wren --sql \"SELECT * FROM <model> LIMIT 5\"`\n\n---\n\n## Workflow 4: After MDL changes\n\nWhen model YAML files are updated, rebuild and re-index:\n\n```bash\n# 1. Validate changes\nwren context validate\n\n# 2. Rebuild manifest\nwren context build\n\n# 3. Re-index schema memory\nwren memory index\n\n# 4. Verify\nwren --sql \"SELECT * FROM <changed_model> LIMIT 1\"\n```\n\n---\n\n## Command decision tree\n\n```text\nGet data back           → wren --sql \"...\"\nAggregation across dims → wren cube query --cube <name> --measures <m> (if cube defined)\nRanked / top-N          → wren cube query ... --order-by \"<measure>:desc\" --limit N\nSee translated SQL only → wren dry-plan --sql \"...\" (accepts -d <datasource> if no active profile)\nValidate against DB     → wren dry-run --sql \"...\"\nSchema context          → wren memory fetch -q \"...\"\nFilter by type/model    → wren memory fetch -q \"...\" --type T --model M --threshold 0\nStore confirmed query   → wren memory store --nl \"...\" --sql \"...\"\nFew-shot examples       → wren memory recall -q \"...\"\nIndex stats             → wren memory status\nRe-index after MDL change → wren memory index\nShow project context    → wren context show\nRebuild manifest        → wren context build\nCheck profile           → wren profile debug\nSwitch profile          → wren profile switch <name>\n```\n\n---\n\n## Cube Query Workflow\n\nWhen the user asks an aggregation question (e.g., \"total revenue by month\",\n\"top customers\"), check if the MDL defines cubes before writing raw SQL.\n\n### Step 1: Discover cubes\n\n```bash\nwren cube list\n```\n\nIf cubes exist and cover the user's question, prefer cube query over raw SQL.\nLower error rate, especially for small / local models — agents don't have to\nhand-write GROUP BY / DATE_TRUNC.\n\n### Step 2: Inspect cube structure\n\n```bash\nwren cube describe <cube_name>\n```\n\nShows the cube's baseObject, measures (with expressions), dimensions,\ntime dimensions, and hierarchies.\n\n### Step 3: Match user's question to cube measures + dimensions\n\n| User phrase | Maps to |\n|---|---|\n| \"total revenue\" | `--measures total` |\n| \"by month\" | `--time-dimension \"order_date:month\"` |\n| \"in 2024\" | `--time-dimension \"order_date:month:2024-01-01,2025-01-01\"` |\n| \"for completed orders\" | `--filter \"status:eq:completed\"` |\n| \"top N customers\" | `--dimensions customer --order-by \"total:desc\" --limit N` |\n| \"worst / lowest / bottom N\" | `--order-by \"<measure>:asc\" --limit N` |\n| \"sorted by name\" | `--order-by \"<dimension>:asc\"` |\n\n### Step 4: Execute via CLI flags OR JSON input\n\nCLI flags:\n\n```bash\nwren cube query \\\n  --cube revenue \\\n  --measures total,order_count \\\n  --time-dimension \"order_date:month:2024-01-01,2025-01-01\" \\\n  --filter \"status:eq:completed\" \\\n  --order-by \"total:desc\" \\\n  --limit 100\n```\n\n**`--limit` without `--order-by` is not a top-N.** It returns an arbitrary N rows\n(or the earliest N by the time dimension when one is present). Whenever the user\nasks for \"top\", \"best\", \"worst\", or \"largest\", add `--order-by`.\n\nOrdering rules: the member must be one this query already selects (a measure,\ndimension, or time dimension); direction is lowercase `asc` or `desc`; each member\nmay appear once. Omitting `--order-by` keeps the previous default ordering.\n\nName the member as the cube declares it. A time dimension appears in the generated\nSQL as `<name>__<granularity>`, but `--order-by` wants the declared name:\n`--time-dimension \"order_date:month\" --order-by \"order_date:desc\"`.\n\nJSON input (good for agent-generated structured queries):\n\n```bash\necho '{\"cube\":\"revenue\",\"measures\":[\"total\"]}' | wren cube query --from -\n```\n\nAdd `--sql-only` to print the generated SQL without executing — useful for\nverification before paying for execution on a remote warehouse.\n\n### Step 5: Error recovery\n\n| Error | Action |\n|---|---|\n| `Unknown measure 'X'` | `wren cube describe <cube>` for available measures |\n| `Unknown dimension 'X'` | `wren cube describe <cube>` for available dimensions |\n| `Cube 'X' not found` | `wren cube list` |\n| `Cannot order by member 'X': member is not selected by the query` | Add `X` to `--measures` / `--dimensions`, or order by a member already selected |\n| `Cannot order by member 'X' more than once` | Drop the duplicate — each member may appear once in `--order-by` |\n| `` unknown variant `DESC`, expected `asc` or `desc` `` | Directions are lowercase: use `desc`, not `DESC` |\n| `Circular dependency detected` | Derived measure references itself — inspect the cube YAML |\n\n### When NOT to use cube query\n\nFall back to `wren --sql` when:\n\n- Custom JOINs across multiple models\n- Window functions, CTEs, or subqueries\n- Queries with no aggregation\n- No cubes defined in the MDL\n\n---\n\n## Aggregation decision tree\n\n```text\nUser question → Is it an aggregation question?\n                (SUM, COUNT, AVG, GROUP BY, \"by month\", \"per customer\", ...)\n  ├── Yes → Are cubes defined? (`wren cube list` once at start of session)\n  │         ├── Yes → Does a cube cover the question? (`wren cube describe`)\n  │         │         ├── Yes → Use `wren cube query` (preferred — lower error rate)\n  │         │         └── No  → Write raw SQL with `wren --sql`\n  │         └── No  → Write raw SQL with `wren --sql`\n  └── No  → Write raw SQL with `wren --sql` (look for memory recall first)\n```\n\n---\n\n## Things to avoid\n\n- Do not guess model or column names — check context first\n- Do not store failed queries or queries the user said are wrong\n- Do not skip storing successful queries with a clear NL question — default is to store\n- Do not re-index before every query — once per MDL change\n- Do not pass passwords via `--connection-info` if shell history is shared — use profiles (`wren profile add`) or `--connection-file`\n","evals/spodbtify_ab/README.md":"# Spodbtify A/B Eval\n\nThis eval compares two workflows for turning the same DuckDB database into a\nqueryable Wren semantic layer, then scoring how well an AI coding agent answers\n20 analytical Spotify playlist questions.\n\nThe eval is intentionally agent-agnostic. It is not tied to Claude Code: use\nthe same spec with Claude, Codex, Cursor, an MCP client, or any runner that can\nconsume a prompt file and write an answer file.\n\n## Dataset\n\nExpected local dataset:\n\n```bash\nexport SPODBTIFY_DUCKDB_PATH=/path/to/spodbtify.duckdb\n```\n\nExpected dbt project artifacts:\n\n```bash\nexport SPODBTIFY_DBT_PROJECT_DIR=/path/to/spodbtify-dbt-project\n\n$SPODBTIFY_DBT_PROJECT_DIR/target/manifest.json\n$SPODBTIFY_DBT_PROJECT_DIR/target/catalog.json\n$SPODBTIFY_DBT_PROJECT_DIR/target/run_results.json\n$SPODBTIFY_DBT_PROJECT_DIR/target/compiled/\n```\n\nThe dataset is intentionally not checked into this repository.\n\n## Files\n\n- `spodbtify_ab_eval.json` is the canonical eval spec: workflows, controls,\n  table inventory, scoring rubric, questions, and result schema.\n- `agent_output.schema.json` is an optional JSON Schema for agents that support\n  structured output.\n- `run_eval.py` validates the spec, generates prompts, runs arbitrary agent\n  command templates, and summarizes score files.\n\n## Quick Validation\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py validate\n```\n\n## Generate Prompts\n\nPrint one prompt:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py prompt \\\n  --agent codex \\\n  --workflow dbt_integrated \\\n  --question 1\n```\n\nMaterialize prompt files for all questions without running an agent:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py run-agent \\\n  --agent codex \\\n  --workflow both \\\n  --dry-run\n```\n\n## Run With Any Agent CLI\n\n`run-agent` accepts a shell command template. These placeholders are available:\n\n- `{prompt_file}`\n- `{output_file}`\n- `{agent}`\n- `{workflow}`\n- `{question_id}`\n- `{schema_file}`\n\nExample shape:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py run-agent \\\n  --agent codex \\\n  --workflow schema_only \\\n  --command 'codex exec --sandbox danger-full-access --output-schema {schema_file} -o {output_file} - < {prompt_file}'\n```\n\nUse the equivalent command for Claude or another agent:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py run-agent \\\n  --agent claude \\\n  --workflow dbt_integrated \\\n  --command 'claude -p \"$(cat {prompt_file})\" > {output_file}'\n```\n\nExact CLI flags vary by tool and installation. The eval contract is the prompt\nfile in, answer file out. Keep one fresh session per workflow and do not share\nanswers or memory between workflows.\n\n## Scoring Contract\n\nAgents should return JSON with:\n\n```json\n{\n  \"question_id\": 1,\n  \"workflow\": \"dbt_integrated\",\n  \"agent\": \"codex\",\n  \"selected_tables\": [\"Top_Artists\", \"Artist_Collaborators\"],\n  \"sql\": \"SELECT ...\",\n  \"answer\": \"The analytical answer...\",\n  \"notes\": \"Optional caveats\"\n}\n```\n\nAfter a human or external grader assigns the three 0/1/2 scores for each\nquestion, store them in the score schema shown by:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py new-score-template \\\n  --agent codex \\\n  --workflow dbt_integrated \\\n  --output /tmp/codex-dbt-score.json\n```\n\nThen summarize:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py score --scores /tmp/codex-dbt-score.json\n```\n\nDo not commit agent answer files, run directories, or scored result files.\n","skills/.claude-plugin/marketplace.json":"{\n  \"name\": \"wren\",\n  \"owner\": {\n    \"name\": \"Canner\",\n    \"email\": \"dev@cannerdata.com\"\n  },\n  \"metadata\": {\n    \"description\": \"Wren Engine — discovery stub for AI agents. Actual workflow guides and prompt helpers ship inside the wren CLI (pip install wrenai) and are served by `wren skills get` / `wren ask`.\"\n  },\n  \"plugins\": [\n    {\n      \"name\": \"wren\",\n      \"source\": \".\",\n      \"description\": \"Wren CLI for AI agents — semantic SQL over 22+ databases. Installs a single discovery stub that points the agent at the wren CLI for all workflow content.\",\n      \"version\": \"1.0.0\",\n      \"keywords\": [\n        \"wren\",\n        \"sql\",\n        \"mdl\",\n        \"cli\",\n        \"semantic-layer\",\n        \"postgres\",\n        \"bigquery\",\n        \"snowflake\",\n        \"mysql\",\n        \"clickhouse\",\n        \"trino\",\n        \"mssql\",\n        \"databricks\",\n        \"redshift\",\n        \"spark\",\n        \"athena\",\n        \"oracle\",\n        \"duckdb\"\n      ]\n    }\n  ]\n}\n","skills/.claude-plugin/plugin.json":"{\n  \"name\": \"wren\",\n  \"description\": \"AI agent skills for Wren Engine CLI — semantic SQL layer for 22+ data sources.\",\n  \"version\": \"1.0.0\",\n  \"author\": {\n    \"name\": \"Canner\",\n    \"url\": \"https://www.getwren.ai/\"\n  },\n  \"homepage\": \"https://www.getwren.ai/\",\n  \"repository\": \"https://github.com/Canner/WrenAI\",\n  \"license\": \"Apache-2.0\",\n  \"keywords\": [\n    \"wren\",\n    \"sql\",\n    \"mdl\",\n    \"cli\",\n    \"semantic-layer\",\n    \"data-source\",\n    \"postgres\",\n    \"bigquery\",\n    \"snowflake\",\n    \"mysql\",\n    \"clickhouse\",\n    \"trino\",\n    \"mssql\",\n    \"databricks\",\n    \"redshift\",\n    \"spark\",\n    \"athena\",\n    \"oracle\",\n    \"duckdb\"\n  ]\n}\n","skills/AUTHORING.md":"# Skill Authoring Guide\n\n> **Where to author new skill content:**\n> **NOT here under `skills/`.** New skill guides ship as Python package data\n> in [`core/wren/src/wren/skills_content/<name>/`](../core/wren/src/wren/skills_content/),\n> served at runtime by `wren skills get <name>`. That keeps the content\n> version-aligned with the installed `wren` CLI — no skill cache, no\n> `versions.json` drift hack.\n>\n> This `skills/` tree only contains the **distribution stubs** that AI clients\n> install (one new discovery stub `wren/`, plus five deprecated redirect\n> stubs kept for one release).\n\nThe skill content format below still follows the\n[Agent Skills](https://agentskills.io/) open spec — the only thing that\nchanged is where the files live and how they reach the agent.\n\n---\n\n## Directory layout (under `core/wren/src/wren/skills_content/`)\n\n```text\n<name>/\n├── SKILL.md              # Required — frontmatter + workflow instructions\n├── references/           # Optional — reference docs, served by `wren skills get <name> --full`\n│   ├── some-topic.md\n│   └── another-topic.md\n└── scripts/              # Optional — bundled scripts, served by `wren skills get <name> --script <s>`\n```\n\n---\n\n## Frontmatter\n\n```yaml\n---\nname: <name>              # matches the parent directory; lowercase, hyphens\ndescription: \"What this skill does and when to trigger it. Include trigger\n  keywords so an AI client can match it to user intent.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n```\n\n**Drop the `version:` field.** Content version = the installed wrenai\nversion, since the SKILL.md ships inside the wheel.\n\n---\n\n## Progressive disclosure\n\n| Tier | Content | Loaded when |\n|------|---------|-------------|\n| 1 — Discovery stub | `skills/wren/SKILL.md` frontmatter description (~few hundred tokens) | At every AI-client session start |\n| 2 — Main guide | `wren skills get <name>` | The agent runs the command when the user's task matches |\n| 3 — References | `wren skills get <name> --full` | The agent opts in when it needs depth |\n| 3 — Scripts | `wren skills get <name> --script <s>` | Same |\n\nKeep `SKILL.md` under ~500 lines. Move reference-only content into `references/`.\n\n---\n\n## What goes where\n\n### Keep in `SKILL.md`\n- Step-by-step workflow the agent follows\n- Decision criteria and branching logic\n- Short commands the agent needs immediately\n- Quick reference tables (file paths, phase mappings, etc.)\n\n### Move to `references/`\n- Output templates (report formats, plan file formats)\n- Per-case investigation details\n- Large lookup tables\n- Anything that would push `SKILL.md` past ~500 lines\n\nCross-link inside the same skill by reference name (delivered via `--full`):\n```markdown\nFor the CTE rewrite pipeline, see the `wren-sql` reference (run\n`wren skills get usage --full`).\n```\n\nCross-link to another skill via its CLI command:\n```markdown\nFor day-to-day querying after setup, run `wren skills get usage`.\n```\n\nCross-link to a general doc:\n```markdown\nFor troubleshooting, see [`connect.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md).\n```\n\n---\n\n## Naming\n\n| Item | Convention | Example |\n|------|------------|---------|\n| Skill directory | `kebab-case` | `generate-mdl/` |\n| `name` field | same as directory | `generate-mdl` |\n| Reference files | descriptive `kebab-case` | `memory.md`, `wren-sql.md` |\n| Script files | descriptive `kebab-case` | `introspect_dlt.py` |\n\n---\n\n## Registering a new skill\n\n1. Create the directory under `core/wren/src/wren/skills_content/<name>/` with `SKILL.md` (and optional `references/`, `scripts/`).\n2. Author the content following the format above.\n3. Add a test to `core/wren/tests/unit/test_skills_cli.py` verifying `wren skills get <name>` returns the guide and `--full` inlines its references (if any).\n4. Done — there is no separate `versions.json` / `index.json` to update; the package-data ships with the wheel and `wren skills list` enumerates it automatically.\n\nNo `bash skills/check-versions.sh` step — version drift is impossible by\nconstruction (content travels with the binary).\n","skills/README.md":"# Wren Engine — Agent Skill Distribution\n\nThe actual skill content (workflow guides, reference docs, prompt helpers)\n**lives inside the `wren` CLI**. This directory ships a single discovery stub\nthat an AI client installs once; the stub then tells the agent to fetch\neverything else from the CLI at runtime (so content always matches the\ninstalled wrenai version).\n\nSee [`SKILLS.md`](SKILLS.md) for the full design and command surface.\n\n## Install\n\n### The CLI itself (where all skill content lives)\n\n```bash\npip install wrenai\n```\n\n### The discovery stub (so an AI client knows the CLI exists)\n\n#### Option 1 — Claude Code plugin\n\n```text\n/plugin marketplace add Canner/WrenAI --path skills\n/plugin install wren@wren\n```\n\n#### Option 2 — `npx skills`\n\n```bash\nnpx skills add Canner/WrenAI\n```\n\nThe installer auto-detects your AI client. To target a specific one, add\n`--agent <name>` (e.g. `claude-code`, `cursor`, `windsurf`, `cline`).\n\n#### Option 3 — local install script\n\n```bash\nbash skills/install.sh                 # install the discovery stub\nbash skills/install.sh --force         # overwrite existing\n```\n\n## What the agent does with the stub\n\nOnce installed, the agent reads `wren/SKILL.md` and learns to call:\n\n```bash\nwren skills list                        # discover workflow guides\nwren skills get onboarding              # fetch a guide (one of 5 names)\nwren docs connection-info <ds>          # connection fields for a data source\nwren ask \"<question>\" --guided|--direct # wrap a prompt for an agent\n```\n\n## Requirements\n\n- `wren` CLI installed (`pip install wrenai` or `pip install \"wrenai[<extras>]\"`)\n- A database connection (configured via `wren profile add`)\n- An AI client that supports skills (Claude Code, Cursor, Cline, etc.)\n","skills/SKILLS.md":"# Wren Engine — Agent Skills\n\nThe actual workflow guides, reference docs, and prompt helpers live **inside\nthe `wren` CLI itself**, so they always match the installed wrenai\nversion (no skill cache, no version drift).\n\nThis directory ships a single discovery stub ([`wren/SKILL.md`](wren/SKILL.md))\nthat an AI client can install. Once the agent reads the stub, it learns to\nfetch everything else from the CLI on demand:\n\n```bash\nwren skills list                        # all available workflow guides\nwren skills get <name>                  # fetch a guide\nwren skills get <name> --full           # include the guide's reference docs\nwren skills get <name> --script <s>     # fetch a bundled script\n\nwren docs connection-info <ds>          # connection fields for a data source\n\nwren ask \"<question>\" --guided          # wrap a question for a weaker LLM\nwren ask \"<question>\" --direct          # wrap a question for a stronger LLM\n```\n\n## Install\n\n```bash\npip install wrenai                 # the CLI (everything is here)\nnpx skills add Canner/WrenAI            # install the discovery stub for AI clients\n```\n\nOr via Claude Code's plugin marketplace:\n\n```text\n/plugin marketplace add Canner/WrenAI --path skills\n/plugin install wren@wren\n```\n\n## Writing a new skill\n\nNew skill guides ship as Python package data in\n[`core/wren/src/wren/skills_content/<name>/`](../core/wren/src/wren/skills_content/),\nnot as a new directory under this `skills/` tree. See\n[`AUTHORING.md`](AUTHORING.md).\n","skills/index.json":"{\n  \"name\": \"wrenai\",\n  \"description\": \"Discovery stub for the Wren AI agent skill bundle. The actual workflow guides and prompt helpers live inside the wren CLI (`pip install wrenai`) and are served by `wren skills get` / `wren ask`.\",\n  \"homepage\": \"https://wren.ai\",\n  \"repository\": \"https://github.com/Canner/WrenAI\",\n  \"license\": \"Apache-2.0\",\n  \"skills\": [\n    {\n      \"name\": \"wren\",\n      \"description\": \"Wren CLI discovery stub. The actual workflow guides live inside the wren CLI; this stub teaches an AI agent to call `wren skills list`, `wren skills get <name>`, and `wren ask <prompt> --guided|--direct`. Use whenever the user asks a data question, wants to install / set up Wren Engine, connect a new database, load SaaS data via dlt, generate or regenerate an MDL project, enrich a project with business context, or build & deploy a shareable GenBI web app / dashboard from a project's semantic layer (Vercel / Cloudflare).\",\n      \"tags\": [\n        \"wren\",\n        \"discovery-stub\",\n        \"cli\",\n        \"sql\",\n        \"mdl\",\n        \"semantic-layer\",\n        \"genbi\"\n      ],\n      \"repository\": \"https://github.com/Canner/WrenAI/tree/main/skills/wren\"\n    }\n  ]\n}\n","skills/wren/SKILL.md":"---\nname: wren\ndescription: \"Wren CLI for AI agents — a semantic SQL layer over 22+ databases (Postgres, MySQL, BigQuery, Snowflake, Spark, …). The actual workflow guides live inside the `wren` CLI itself; this is just a discovery stub. Use whenever the user asks a data question (how many, show me, top N, compare, trend, breakdown, metric, revenue, customers, orders), wants to install / set up Wren Engine, connect a new database, connect SaaS data via dlt (HubSpot, Stripe, Salesforce, GitHub, Slack), generate or regenerate an MDL project from a database schema, enrich a project with business context (enum meanings, units, cubes like ARR / DAU / churn), or turn a project's context layer into a shareable GenBI web app / dashboard and deploy it to Vercel or Cloudflare. Triggers: 'install wren', 'set up wren engine', 'connect database to wren', 'connect SaaS to wren', 'load hubspot / stripe / salesforce data', 'generate mdl', 'scaffold wren project', 'enrich wren context', 'augment my project', 'add cubes', 'build a dashboard', 'make a shareable analytics app', 'deploy my context layer as a web app', 'genbi app', 'wren onboarding', 'wren usage', 'wren generate mdl', 'wren dlt connector', 'wren enrich context', 'wren genbi'.\"\nlicense: Apache-2.0\nallowed-tools: Bash(wren:*)\n---\n\n# Wren CLI\n\nThis is a discovery stub. The actual workflow guides and prompt helpers\nlive inside the `wren` CLI itself, so they always match the installed\nwrenai version (no skill cache, no version drift).\n\nInstall: `pip install wrenai`.\n\n## Workflow guides\n\n```bash\nwren skills list                        # all available workflow guides\nwren skills get onboarding              # set up Wren end-to-end\nwren skills get usage                   # day-to-day querying\nwren skills get generate-mdl            # generate MDL from a database schema\nwren skills get dlt-connector           # connect SaaS sources via dlt\nwren skills get enrich-context          # add business context (units, enums, cubes)\nwren skills get genbi                   # build & deploy a shareable GenBI web app\n# add --full to include the skill's reference docs\n# add --script <name> to fetch a bundled script (e.g. dlt-connector / introspect_dlt)\n```\n\n## Reference docs\n\nFull reference docs live on the web: <https://github.com/Canner/WrenAI/tree/main/docs/core>\n\n```bash\nwren docs connection-info <ds>          # required + optional connection fields for a data source\n```\n\n## Prompt enhancement (wraps a user question for an agent)\n\n```bash\nwren ask \"<question>\" --guided          # for weaker LLMs (strict task flow)\nwren ask \"<question>\" --direct          # for stronger LLMs (minimal wrapping)\n```\n\n## Day-to-day data commands (not a sub-app — top-level)\n\n```bash\nwren --sql '...'                        # execute SQL through the MDL layer\nwren query --sql '...'                  # same, explicit\nwren dry-plan --sql '...'               # transpile only, no DB hit\nwren context show / build / validate    # project / MDL lifecycle\nwren profile add / list / switch        # named connection profiles\nwren memory index / recall / store      # semantic memory (needs `[memory]` extra)\n```\n\nRun `wren --help` for the full surface; load the matching `wren skills get\n<name>` guide before driving any multi-step workflow.\n"},"files":{".claude/CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nWren is an open-source semantic engine for MCP clients and AI agents. It translates SQL queries through a semantic layer (MDL — Modeling Definition Language) and executes them against 22+ data sources (PostgreSQL, BigQuery, Snowflake, Spark, etc.). The Rust engine is powered by Apache DataFusion (upstream, crates.io v55).\n\nThe previous WrenAI services (`wren-ui/`, `wren-ai-service/`, `wren-launcher/`, `docker/`, `deployment/`) were moved to the `legacy/v1` branch (tag `v1-final`) as of the wren-engine import. Active development is focused on the Open Context Engine.\n\n## Repository Structure\n\n```\ncore/\n├── wren-core/        Rust semantic engine (Cargo workspace; crates.io: wren-semantic-core, lib name wren_core)\n├── wren-core-base/   Shared Rust crate — manifest types (Model, Column, Metric, Relationship, View) + ManifestBuilder (crates.io: wren-core-base)\n├── wren-core-py/     PyO3 bindings exposing wren-core to Python (PyPI: wren-core)\n├── wren-core-wasm/   WebAssembly build of wren-core for in-browser semantic SQL (npm: wren-core-wasm)\n├── wren/             Python SDK and CLI — `wren` command, profile/context/memory management (PyPI: wrenai)\n└── wren-mdl/         MDL JSON schema definition\n\ndocs/core/            Module documentation\nexamples/             Example projects (placeholder — to be populated)\nskills/               CLI-based agent skills (wren-generate-mdl, wren-usage, wren-dlt-connector, wren-onboarding)\nscripts/              Repo helper scripts\n```\n\n## Build & Development Commands\n\n### core/wren-core (Rust)\n```bash\ncd core/wren-core\ncargo check --all-targets                                  # compile check\ncargo test --lib --tests --bins                            # tests (set RUST_MIN_STACK=8388608)\ncargo fmt --all                                            # format\ncargo clippy --all-targets --all-features -- -D warnings   # lint\ntaplo fmt                                                  # format Cargo.toml files\n```\n\nMost unit tests live in `core/wren-core/core/src/mdl/mod.rs`. SQL end-to-end tests use sqllogictest files in `core/wren-core/sqllogictest/test_files/`.\n\n### core/wren-core-py (Python bindings)\n```bash\ncd core/wren-core-py\njust install      # uv sync (deps only; --no-install-project)\njust develop      # build dev wheel with maturin\njust test-rs      # Rust tests (cargo test --no-default-features)\njust test-py      # Python tests (pytest)\njust test         # both\njust format       # cargo fmt + ruff + taplo\n```\n\n### core/wren-core-wasm (WASM)\n```bash\ncd core/wren-core-wasm\njust build        # wasm-pack build (browser target)\njust test         # wasm-pack test\n```\nOutputs a ~68 MB WASM binary; distributed via npm and unpkg (jsDelivr's 50 MB per-file CDN limit blocks it).\n\n### core/wren (SDK & CLI)\n```bash\ncd core/wren\njust install              # uv sync (locked prebuilt wren-core-py wheel from PyPI; no Rust build)\njust install-all          # with all optional extras (incl. memory)\njust install-extra <e>    # e.g. just install-extra postgres\njust install-memory       # memory extra (lancedb + sentence-transformers)\njust install-local        # engine dev: uv sync + build local wheel + overlay\njust use-local-core       # rebuild + re-overlay after Rust changes\njust dev                  # run `wren` CLI\njust test                 # pytest tests/\njust test-memory          # memory-specific tests\njust lint                 # ruff format --check + ruff check\njust format               # ruff auto-fix\njust build                # uv build (produces wheel)\n```\n\nUses `uv` (not Poetry). `pyproject.toml` uses `hatchling` as build backend. Optional extras: `postgres`, `mysql`, `bigquery`, `snowflake`, `clickhouse`, `trino`, `mssql`, `databricks`, `redshift`, `spark`, `athena`, `oracle`, `memory`, `all`, `dev`.\n\n## Architecture: Query Flow\n\n```\nSQL query\n  → wren CLI / wren-core-py\n  → wren-core (Rust): MDL analysis → logical plan → optimization\n  → DataFusion (query planning, upstream crates.io v55)\n  → connector-specific SQL (Ibis / sqlglot)\n  → native execution on the target data source\n```\n\n## Key Architecture Details\n\n**wren-core internals** (`core/wren-core/core/src/`):\n- `mdl/` — Core MDL processing: `WrenMDL` (manifest + symbol table), `AnalyzedWrenMDL` (with lineage), function definitions per dialect (scalar/aggregate/window), type planning\n- `logical_plan/analyze/` — DataFusion analyzer rules: `ModelAnalyzeRule` (TableScan → ModelPlanNode), scope tracking, access control (RLAC/CLAC), view expansion, relationship chain resolution\n- `logical_plan/optimize/` — Optimization passes: type coercion, timestamp simplification\n- `sql/` — SQL parsing and analysis\n\n**Manifest types** (`core/wren-core-base/src/mdl/`):\n- `manifest.rs` — `Manifest`, `Model`, `Column`, `Metric`, `Relationship`, `View`, `RowLevelAccessControl`, `ColumnLevelAccessControl`\n- `builder.rs` — Fluent `ManifestBuilder` API\n- Uses `wren-manifest-macro` for auto-generating Pydantic-compatible Python classes\n\n## Known wren-core Limitations\n\n**ModelAnalyzeRule — correlated subquery column resolution**: cannot resolve outer column references inside correlated subqueries; only sees the subquery's own table scope. Affects TPCH Q2, Q4, Q15, Q17, Q20, Q21, Q22.\n\n## Conventions\n\n- **Commits**: Conventional commits (`feat:`, `fix:`, `chore:`, `refactor:`, `test:`, `docs:`, `perf:`, `deps:`). Releases are automated via release-please with independent release lines per module.\n- **Rust**: format with `cargo fmt`, lint with `clippy -D warnings`, TOML with `taplo`.\n- **Python**: format and lint with `ruff` (line-length 88, target Python 3.11). Both `core/wren-core-py` and `core/wren` use uv.\n- **DataFusion**: upstream `datafusion` v55 from crates.io (no longer the Canner fork).\n- **Snapshot testing**: wren-core uses `insta` for Rust snapshot tests.\n- **CI**: Per-module path-filtered workflows trigger only on changes inside that module.\n\n## Contribution Bar\n\nRead this before opening a pull request. These rules exist because a large share of\nincoming PRs fail them; a PR that violates one will be closed rather than iterated on.\n\n### Prove the problem before fixing it\n\n- **Do not assert the behaviour of an external system you have not observed.** If you\n  claim an engine, driver, or library rejects some input, reproduce it and paste the\n  actual error into the description. Unevidenced claims — \"the parser rejects a trailing\n  semicolon\", \"some clients reject this\" — are rejected on sight; several such claims\n  have turned out to be false when tested.\n- **Do not add a guard for a state you have not shown to be reachable.** Trace the call\n  path and say in the description how the bad value gets there. Where the repo already\n  validates (see \"Validation boundaries\" in `core/wren/.claude/CLAUDE.md`), re-validating\n  downstream is dead code.\n- If you cannot reproduce a failure, you have a hypothesis, not a bug. **Open an issue\n  with the reproduction attempt, not a PR.**\n\n### Label honestly\n\n- `fix:` requires a reproducible failure that the change repairs. A change with no\n  observable difference in behaviour is `refactor:` or `chore:`.\n- The description must say what changes for a user. \"Improves robustness\" and\n  \"hardens X\" are not answers.\n\n### Tests must exercise the code you changed\n\n- A test that reads a source file as text and asserts on substrings tests nothing. Call\n  the function; assert on what it returns, or on what it passes downstream.\n- Every assertion must be capable of failing. An assertion about a condition the code\n  cannot produce is noise.\n- Confirm the test actually runs in CI. Some test files are gated behind optional extras\n  or containers and are skipped by the default jobs — a service-free test placed in one\n  of those files never executes.\n\n### One change, once\n\n- Search open PRs before opening a new one. Duplicate PRs against the same file are\n  closed without review.\n- A mechanical change repeated across several files or connectors belongs in **one** PR,\n  not one PR per file. Reviewers need to see the resulting convention in a single diff.\n\n### Respect decisions already recorded\n\n- If a comment or a test documents a deliberate choice, do not silently reverse it.\n  Quote it in the description and argue why it should change.\n\n### Keep the branch current\n\n- Rebase onto the target branch before requesting review. A branch whose last CI run was\n  against an old base can merge cleanly and still be broken — a textually clean merge is\n  not a semantically correct one.\n","AGENTS.md":"# AGENTS.md\n\nRepository-wide instructions live in `.claude/CLAUDE.md`. Please refer to that file for shared build commands, architecture, and conventions.\n\n**Before opening a pull request, read the \"Contribution Bar\" section of that file.** It sets out what is expected of a change here — reproduce a failure before claiming one, label honestly, make tests exercise the code they cover — and what will be closed rather than reviewed.\n\nPer-module instructions live in `core/<module>/.claude/CLAUDE.md`, each with its own `AGENTS.md` pointing at it.\n","core/wren/src/wren/skills_content/dlt-connector/SKILL.md":"---\nname: dlt-connector\ndescription: \"Connect SaaS data (HubSpot, Stripe, Salesforce, GitHub, Slack, etc.) to Wren Engine for SQL analysis. Guides the user through the full flow: install dlt, pick a SaaS source, set up credentials, run the data pipeline into DuckDB, then auto-generate a Wren semantic project from the loaded data. Use this skill whenever the user mentions: connecting SaaS data, importing data from an API, dlt pipelines, loading HubSpot/Stripe/Salesforce/GitHub/Slack data, querying SaaS data with SQL, or setting up a new data source from a REST API. Also trigger when the user already has a dlt-produced DuckDB file and wants to create a Wren project from it.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# wren-dlt-connector\n\n> Reference docs (`dlt_sources`) and the `introspect_dlt` script are bundled. Pull references with `wren skills get dlt-connector --full`; fetch a script with `wren skills get dlt-connector --script <name>`.\n\nConnect SaaS data to Wren Engine for SQL analysis — from zero to a verified, queryable project in one conversation.\n\n## Who this is for\n\nData analysts who know SQL and some Python, but may not have used dlt or Wren before. Explain concepts briefly when they first appear, but don't over-explain things a SQL-literate person would already know.\n\n## Overview\n\nThis skill walks through a four-phase workflow:\n\n1. **Extract** — Use dlt (data load tool) to pull data from a SaaS API into a local DuckDB file\n2. **Model** — Introspect the DuckDB schema and auto-generate a Wren semantic project (YAML models, relationships, profile)\n3. **Build & Verify** — Build the project and run actual SQL queries to confirm everything works end-to-end\n4. **Handoff** — Show the user their data and next steps\n\nThe user might enter at any phase. Ask which phase they're starting from — they may already have a `.duckdb` file and just need phases 2–4.\n\n**The goal is a project that actually queries successfully, not just files that look correct.** Always run the verification step before declaring success.\n\n## Critical: DuckDB catalog naming\n\nWhen wren engine connects to a DuckDB file, it ATTACHes it using the filename (without `.duckdb` extension) as the catalog alias:\n\n```\nATTACH DATABASE 'stripe_data.duckdb' AS \"stripe_data\" (READ_ONLY)\n```\n\nThis means **every model's `table_reference.catalog` must equal the DuckDB filename stem**. If the file is `hubspot.duckdb`, the catalog is `hubspot`. If it's `my_pipeline.duckdb`, the catalog is `my_pipeline`.\n\nGetting this wrong causes \"table not found\" errors at query time. The `introspect_dlt.py` script handles this automatically.\n\n## Critical: Type normalization\n\nColumn types must be normalized using wren SDK's `type_mapping.parse_type()` function, which uses sqlglot to convert database-specific types (like DuckDB's `HUGEINT`, `TIMESTAMP WITH TIME ZONE`) into canonical SQL types that wren-core understands. Do not hardcode type mappings — always delegate to `parse_type(raw_type, \"duckdb\")`.\n\nThe `introspect_dlt.py` script does this automatically when wren SDK is installed.\n\n## Phase 1: Extract — dlt Pipeline Setup\n\n### Step 1: Pick the SaaS source\n\nAsk the user which SaaS service they want to connect. Read `dlt_sources` for a list of popular verified sources and their auth requirements. If the source isn't listed, check whether dlt has a verified source for it by searching `dlthub.com/docs/dlt-ecosystem/verified-sources`.\n\n### Step 2: Install dlt\n\n```bash\npip install \"dlt[duckdb]\" --break-system-packages\n```\n\n### Step 3: Write the pipeline script\n\nCreate a Python script that:\n1. Imports the dlt source function for the chosen SaaS\n2. Configures the pipeline with `destination='duckdb'` and a local file path\n3. Runs the pipeline with `pipeline.run(source)`\n\nHere's the general pattern — adapt it per source (check `dlt_sources` for source-specific templates):\n\n```python\nimport dlt\n\npipeline = dlt.pipeline(\n    pipeline_name=\"<source>_pipeline\",\n    destination=\"duckdb\",\n    dataset_name=\"<source>_data\",\n)\n\n# Source-specific: check the dlt_sources reference for auth patterns\nsource = <source_function>(api_key=dlt.secrets.value)\n\ninfo = pipeline.run(source)\nprint(info)\n```\n\n### Step 4: Set up credentials\n\ndlt reads credentials from environment variables or `.dlt/secrets.toml`. The simplest approach for a one-time run:\n\n```bash\n# Set the credential as an environment variable\n# The exact variable name depends on the source — check the dlt_sources reference\nexport SOURCES__<SOURCE>__API_KEY=\"the-actual-key\"\n```\n\nAsk the user for their API key or token. Remind them:\n- Never commit credentials to git\n- Environment variables are the simplest way for a one-time run\n- For repeated use, they can create `.dlt/secrets.toml`\n\n### Step 5: Run the pipeline\n\n```bash\npython <pipeline_script>.py\n```\n\nAfter the run, confirm:\n1. The pipeline completed without errors\n2. A `.duckdb` file was created (usually at `<pipeline_name>.duckdb`)\n3. Print discovered tables and their column counts\n\n```python\nimport duckdb\n\ncon = duckdb.connect(\"<pipeline_name>.duckdb\", read_only=True)\nfor row in con.execute(\"\"\"\n    SELECT table_schema, table_name,\n           (SELECT COUNT(*) FROM information_schema.columns c\n            WHERE c.table_schema = t.table_schema AND c.table_name = t.table_name) as col_count\n    FROM information_schema.tables t\n    WHERE table_schema NOT IN ('information_schema', 'pg_catalog')\n      AND table_name NOT LIKE '_dlt_%'\n    ORDER BY table_schema, table_name\n\"\"\").fetchall():\n    print(f\"  {row[0]}.{row[1]} ({row[2]} columns)\")\ncon.close()\n```\n\n## Phase 2: Model — Generate Wren Project\n\nRun the introspection script to auto-generate a complete Wren project from the DuckDB file:\n\n```bash\n# first fetch the script: wren skills get dlt-connector --script introspect_dlt > introspect_dlt.py\npython introspect_dlt.py \\\n    --duckdb-path <path-to-duckdb-file> \\\n    --output-dir <project-directory> \\\n    --project-name <name>\n```\n\nThis script:\n- Connects to the DuckDB file (read-only)\n- **Sets `table_reference.catalog` to the DuckDB filename stem** (matching wren engine's ATTACH behavior)\n- Discovers all tables and columns via `information_schema`\n- Filters out dlt internal tables (`_dlt_loads`, `_dlt_pipeline_state`, etc.)\n- Filters out dlt metadata columns (`_dlt_id`, `_dlt_load_id`, `_dlt_list_idx`) from model definitions\n- Detects parent-child relationships from `_dlt_parent_id` columns and table naming conventions\n- **Normalizes column types using `wren.type_mapping.parse_type()`** (sqlglot-based)\n- Generates a complete v5 YAML project (wren_project.yml, models/, relationships.yml, knowledge/rules/)\n\nAfter running, show the user what was generated:\n\n```bash\n# Show project summary\ncat <project-directory>/wren_project.yml\necho \"---\"\nls <project-directory>/models/\necho \"---\"\ncat <project-directory>/relationships.yml\n```\n\n### Verify model correctness\n\nSpot-check one generated model to confirm:\n1. `table_reference.catalog` matches the DuckDB filename (e.g., `stripe_data` for `stripe_data.duckdb`)\n2. `table_reference.schema` matches the DuckDB schema (usually `main`)\n3. No `_dlt_*` columns appear in the columns list\n4. Column types look reasonable (VARCHAR, BIGINT, BOOLEAN, TIMESTAMP, etc.)\n\n### Set up the connection profile\n\nCreate a Wren profile so the user can query without specifying connection details every time. The `url` must point to the **directory containing** the `.duckdb` file (not the file itself):\n\n```python\nimport yaml\nfrom pathlib import Path\n\nwren_home = Path.home() / \".wren\"\nwren_home.mkdir(exist_ok=True)\nprofiles_file = wren_home / \"profiles.yml\"\n\nexisting = (\n    (yaml.safe_load(profiles_file.read_text()) or {}) if profiles_file.exists() else {}\n)\nexisting.setdefault(\"profiles\", {})\n\nprofile_name = \"<source>_dlt\"\nexisting[\"profiles\"][profile_name] = {\n    \"datasource\": \"duckdb\",\n    \"url\": str(Path(\"<duckdb-path>\").resolve().parent),\n    \"format\": \"duckdb\",\n}\nexisting[\"active\"] = profile_name\n\nprofiles_file.write_text(yaml.dump(existing, default_flow_style=False, sort_keys=False))\n```\n\n## Phase 3: Build & Verify — The Project Must Actually Work\n\nThis phase is not optional. A project that generates YAML but fails at query time is not a success.\n\n### Step 1: Build the MDL\n\n```bash\ncd <project-directory>\nwren context build\n```\n\nThis compiles the YAML models into `target/mdl.json`. If this fails, fix the issues before proceeding (see Troubleshooting below).\n\n### Step 2: Validate with a real query\n\nRun at least one query per generated model to confirm the project is functional:\n\n```bash\n# For each model, verify it resolves correctly\nwren --sql 'SELECT COUNT(*) as total FROM \"<table_name>\"'\n```\n\nIf any query fails, debug and fix the model before moving on. Common issues:\n- Wrong catalog in table_reference → \"table not found\"\n- Type mismatch → fix the column type in metadata.yml\n- Missing profile → check `wren profile list`\n\n### Step 3: Run interesting queries\n\nOnce basic queries pass, run 2–3 more interesting queries to show the user what their data looks like:\n\n```bash\n# Preview data\nwren --sql 'SELECT * FROM \"<table_name>\" LIMIT 5'\n\n# If there's a relationship, verify both models are queryable\nwren --sql 'SELECT * FROM \"<parent>\" LIMIT 5'\nwren --sql 'SELECT * FROM \"<child>\" LIMIT 5'\n```\n\nShow the results to the user and explain what they're seeing. This is their first look at the data through Wren — make it count.\n\n### Step 4: Confirm success\n\nOnly after queries return real data, tell the user the setup is complete. Summarize:\n- How many models were created\n- What relationships were detected\n- Which profile is active\n- Example queries they can try next\n\n### Next step: share it as an app\n\nThe project is now DuckDB-backed, which is exactly what GenBI snapshot mode\nwants. If the user wants to turn this data into a shareable dashboard / web app\nand deploy it (Vercel / Cloudflare), hand off to the GenBI workflow:\n`wren skills get genbi`. Its snapshot source is the very `.duckdb` file this\npipeline produced.\n\n## Troubleshooting\n\nIf `wren context build` fails:\n- Check that `data_source: duckdb` is set in `wren_project.yml`\n- Verify the DuckDB file path in the profile is correct\n- Run `wren context validate` for detailed error messages\n\nIf queries fail with \"table not found\":\n- **Most likely cause:** `table_reference.catalog` doesn't match the DuckDB filename. If the file is `pipeline.duckdb`, the catalog must be `pipeline`, not empty string.\n- Check the profile's `url` points to the directory containing the `.duckdb` file\n- Table names with double underscores need quoting: `\"hubspot__contacts\"`\n\nIf queries fail with type errors:\n- Check column types in the model YAML — they should be canonical SQL types (VARCHAR, BIGINT, etc.)\n- Re-run `introspect_dlt.py` with wren SDK installed to get proper type normalization\n\nGeneral:\n- Check that the profile is active: `wren profile list`\n- The DuckDB file might be locked if a dlt pipeline is running — wait for it to finish\n\n## Important notes\n\n- dlt's `_dlt_parent_id` / `_dlt_id` columns are kept in the actual DuckDB tables but hidden from Wren model definitions. They're only used in relationship conditions.\n- DuckDB has a single-writer limitation. Don't run a dlt sync while querying. For concurrent access, dlt should write to a separate file and swap atomically.\n- The generated models use `table_reference` (not `ref_sql`) since they map directly to DuckDB tables created by dlt.\n- Column types are normalized using wren SDK's `parse_type()` with sqlglot's DuckDB dialect. If a type looks wrong, the user can edit the model's `metadata.yml` directly.\n","core/wren/src/wren/skills_content/enrich-context/SKILL.md":"---\nname: enrich-context\ndescription: \"Augment a Wren project with business context that DB schema cannot carry — enum value meanings, units (USD vs cents, ms vs sec), NULL semantics, magic sentinels (-1 = unknown), soft-delete default filters, business synonyms, time-grain / TZ conventions, cross-system identifiers, currency rules, canonical-table preferences, AND named aggregation metrics (ARR, churn, DAU, WAU, NRR) proposed as cubes. Runs in one of two modes selected at session start: `grill` (one question at a time, user-driven) or `auto-pilot` (agent infers and applies, escalates only on conflicts and high-blast-radius additions like new cubes / views / relationships). Reads everything under <project>/raw/ (PDFs, glossaries, handbooks, code, data dictionaries) and optionally samples low-cardinality columns from the live DB (grill mode), compares against the current MDL / cubes / knowledge (rules + NL→SQL pairs), then fills gaps via the ten-category gap catalog and the cube proposal flow. Confirmed findings are written back to the right sink. Use when: user says 'enrich context', 'augment my project', 'grill me on this project', 'auto-fill my context', 'agent doesn't understand our docs / enum values / units / null meanings', 'business context is missing', 'what does status=A mean', 'is this amount in USD or cents', 'we keep getting wrong aggregations', 'add cubes for ARR / DAU / churn', 'we have a handbook / glossary / data dictionary the agent should know'; or after generating an MDL and noticing the agent lacks business semantics.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren Enrich Context — Fill the Business-Context Gap\n\nThis skill exists because most business context never lives in a DB schema — it lives in handbooks, glossaries, finance reports, support playbooks, code comments, Slack rules-of-thumb. The agent reads those raw artifacts, finds what's missing from the Wren project, and **either grills the user one question at a time (grill mode) or applies its best inferences directly and hands over an audit (auto-pilot mode)** before writing back. The output lands in the sinks each project already has — MDL, `cubes/`, `knowledge/rules/`, and `knowledge/sql/` — no new artifact, no new tooling.\n\n## Hard rules — READ FIRST\n\n### Universal (apply to both modes)\n\n1. **Only add, never modify existing.** If you find an existing MDL description / relationship / rule that looks wrong, **do not edit it**. Surface it on the \"please fix manually\" list shown in Step 9.\n2. **Every MDL edit must validate.** Right after any MDL YAML change, run `wren context validate`. If it fails, **revert that single change** and feed the error back. Never leave a project in an invalid state.\n3. **Pre-draft every proposal.** Whether you're showing the draft to the user (grill) or applying it directly (auto-pilot), generate the concrete content — never lazy-ask \"what should the description say?\".\n4. **Be explicit about confidence.** In grill mode, open Lane 3 inference questions with \"I'm guessing — \". In auto-pilot, tag every Lane 3 inference and partial Lane 2 match in the Step 9 audit with confidence (high / med / low) and source.\n\n### Grill mode only\n\n5. **One question at a time. Grill relentlessly.** Walk every gap top-down, resolve one decision before moving to the next. Provide a recommended answer for every question.\n6. **Skip is final for this session.** No pending queue, no nagging next round. If the user wants to revisit, they re-run the skill.\n\n### Auto-pilot mode only\n\n7. **Drop into grill for three cases.** Always interrupt auto-pilot and ask the user when:\n   - (a) **Lane 2 conflict** — raw and current MDL disagree.\n   - (b) **High-blast-radius proposal (any lane)** — new cube, new view, new relationship, or new MDL metric/calculated column. These become public artifacts visible to every future agent session, so blast radius doesn't depend on whether the trigger was raw evidence (Lane 2) or inference (Lane 3).\n   - (c) **Lane 2 routing ambiguity** — you can't confidently pick a sink (MDL / `knowledge/rules/` / `knowledge/sql/` / `cubes/`).\n\n   Everything else: apply directly and log to the audit list.\n\n## Step 0 — Mode selection (before anything else)\n\nBefore touching the project or reading any file, ask the user which mode to run in. Lock the choice for the whole session — **no mid-session switching**; the user re-runs to change.\n\n> Two modes for this session:\n>\n> **a) Grill mode** — I walk every gap with you, one question at a time, proposing a draft and waiting for your accept / edit / skip. You stay in the driver's seat. Best when the raw material is sensitive, when you want to learn what I don't know about your project, or when you'd rather review than re-do.\n>\n> **b) Auto-pilot mode** — I read raw + current context, make my best inferences, and apply them. I'll only stop to grill you on (1) conflicts between raw and existing MDL and (2) high-blast-radius additions like new metrics, views, or relationships. The session ends with a full diff + confidence-tagged inference list for you to audit.\n>\n> Which? (a / b)\n\nRemember the choice as `MODE = grill | autopilot` and use it to branch Steps 6 and 9.\n\n## Preflight\n\n### Step 1 — Choose the Wren project\n\n**Always ask the user which project to enrich before doing anything else** — never assume cwd. A user can have several Wren projects and an ambient `~/.wren` profile that doesn't match the one they want to augment.\n\nOffer concrete hints in the question so the user can answer in one round-trip:\n\n```bash\n# Hint 1 — does cwd look like a project?\ntest -f wren_project.yml && pwd\n\n# Hint 2 — does ~/.wren/config.yml point at a default project?\ngrep -E '^project_path:' ~/.wren/config.yml 2>/dev/null\n```\n\nThen ask:\n\n> Which Wren project do you want me to augment?\n> a) `$PWD` (current directory)         ← if Hint 1 matched\n> b) `<path from ~/.wren/config.yml>`   ← if Hint 2 matched\n> c) something else — paste the absolute path\n\nAfter the user answers, lock the path in for the whole session:\n\n```bash\ncd <chosen-path>\ntest -f wren_project.yml || {\n  echo \"Error: <chosen-path> is not a Wren project (no wren_project.yml).\"\n  exit 1\n}\nwren context show >/dev/null 2>&1 || {\n  echo \"Error: wren context show failed — manifest may be invalid.\"\n  exit 1\n}\n```\n\nIf either check fails, stop and tell the user — suggest `wren skills get onboarding` if it's not a project, or `wren context validate` if the manifest is broken.\n\nFrom this point on, **every command and file path in this skill is relative to the chosen project root**. Do not switch projects mid-session — if the user wants to work a different project, end this session and re-run.\n\n### Step 2 — Detect semantic-memory availability\n\n```bash\nwren memory status 2>/dev/null | grep -q \"Backend: lancedb\"\n```\n\nWriting NL→SQL pairs (`wren memory store` → `knowledge/sql/*.md`) works **regardless** — that\nsink is always open. This checks which recall backend is active, i.e. whether the optional\n`memory` extra is installed (LanceDB = semantic; grep = dependency-free fallback):\n\n- Match (`Backend: lancedb`) → set `MEMORY_AVAILABLE = true`. Semantic recall and `wren memory fetch` are usable, and `wren memory index` builds an embedding index in Step 8.\n- No match (grep backend) → set `MEMORY_AVAILABLE = false`. Skip the semantic read/index paths below; pair writeback still happens via `wren memory store`, and grep recall still works.\n\n### Step 3 — Ensure raw/ folder exists\n\nFrom the project root (cwd is already there from Step 1):\n\n```bash\nmkdir -p raw\n```\n\nIf you just created it (the directory was empty or new):\n\n> I've created `raw/` at the project root. Drop anything you think helps explain this project's business context — PDFs, glossaries, handbooks, financial reports, data dictionaries, sample queries, code with comments, screenshots of dashboards, anything.\n>\n> **Heads-up:** the contents may be sensitive. Decide for yourself whether to commit `raw/` to git — I won't touch `.gitignore`.\n>\n> Tell me when you've added the files and I'll start reading.\n\nWait for the user to confirm before continuing.\n\n## Step 4 — Read everything\n\nRead both sides — the raw material and the current Wren context — before forming any opinion.\n\n### Raw\n\nRead every file under `raw/`. Use whatever capability your agent has natively (text, markdown, code, PDF). If you genuinely can't read a particular file, **tell the user once** which file and suggest converting it to text or pasting the relevant excerpt — then move on to the rest. Do **not** install extra Python packages, **do not** reach for new CLI subcommands.\n\n### Current Wren context\n\n| Source | Command |\n|---|---|\n| MDL (full) | `wren context show --output json` |\n| Business rules | `wren context instructions` (reads `knowledge/rules/` + any legacy `instructions.md`) |\n| Existing cubes (names) | `wren cube list` |\n| Existing cubes (measures + dimensions) | `wren cube describe <cube>` for each name above |\n| NL→SQL pairs | read `knowledge/sql/*.md` directly |\n| (Memory) stored pairs | `wren memory list -n 200 --output json` |\n| (Memory) schema as text | `wren memory describe` |\n\nThe memory rows only matter when `MEMORY_AVAILABLE = true`. Reading cubes is essential before any Lane 3 metric proposal — see `cube_proposals` for the duplication guard.\n\n## Step 4.5 — Ground-truth probe (grill mode default; auto-pilot opt-out)\n\nWhen raw is silent on a column's enum / unit / null / magic / time semantics, the catalog's column-local categories (#1, #2, #3, #5, #7 in `gap_catalog`) can often be settled directly by sampling distinct values from the live DB. Read `gap_catalog` before this step — its *Trigger* column tells you which columns are probe candidates.\n\n**Default policy by mode:**\n\n| Mode | Default | How to override |\n|---|---|---|\n| **Grill** | Probe on. Before the first query, ask the user once: \"I want to sample N columns with `LIMIT 30` each to find enum / sentinel / time-grain values — OK?\" Lock the answer for the session. | User says no → skip Step 4.5 entirely; rely on Lane 2 + Lane 3 instead. |\n| **Auto-pilot** | Probe off. The skill never queries the live DB in auto-pilot mode. | None — user must re-run in grill mode if probe would unblock high-confidence inferences. |\n\n**Candidate selection (no DB call yet):**\n\nA column is a probe candidate when *all* hold:\n\n- Description is empty OR does not yet contain the relevant `[tag]` (catalog write format).\n- Column type / name pattern matches catalog #1 (enum), #3 (NULL), #5 (magic), or #7 (time grain).\n- For #3 and #7, the description also lacks event-vs-record or TZ wording.\n\nCategories #2 (unit), #4, #6, #8, #9, #10 are **not** probable — `SELECT DISTINCT` doesn't reveal units, default filters, synonyms, external mappings, currency conventions, or canonical-table preferences. Those need raw evidence or human judgment.\n\n**Probe query:**\n\n```bash\nwren --sql \"SELECT DISTINCT <col> FROM <model> LIMIT 30\" --output json\n# For magic sentinels (catalog #5), also fetch min/max:\nwren --sql \"SELECT MIN(<col>) AS lo, MAX(<col>) AS hi FROM <model>\" --output json\n```\n\n- **≤ 30 distinct values returned** → enum / sentinel / grain candidate. Draft the `[tag]` line and surface to user (grill) with confidence \"med — probed values, semantics still inferred\".\n- **30 returned (LIMIT hit)** → cardinality too high; not an enum / sentinel candidate. Skip.\n- **Query fails** (permissions, connection, large-table timeout) → do not retry. Log the failure to the audit list, surface in Step 9, continue with Lane 2 + Lane 3 only.\n\n**Safety:**\n\n- Probe each (model, column) at most once per session.\n- Never probe a column that already has a matching `[tag]` line — Universal Rule 1.\n- Probe results stay in working memory; do not write them to disk.\n\n## Step 5 — Three gap-detection lanes (in your head, no artifact)\n\nHold all three lanes in working memory. Do not write a `gaps.yml`.\n\nBefore sweeping, load `gap_catalog` — the ten business-semantic categories the schema cannot carry. Each lane consumes the catalog differently: Lane 1 walks it as type-aware mechanical triggers, Lane 2 classifies each atomic raw claim into one of the 10 categories before routing, Lane 3 seeds inference prompts when a trigger fires but raw is silent.\n\n### Lane 1 — Structural coverage (mechanical)\n\nScan the current MDL and check:\n\n- Every model has a non-empty `properties.description`?\n- Every column has a description (at least for non-PK, non-FK ones)?\n- Every model has a `primary_key`?\n- Every model has at least one relationship (orphan models are suspicious)?\n- `knowledge/rules/` has real content beyond the scaffold default?\n- `knowledge/sql/` has at least a few canonical NL→SQL pairs?\n\nPlus, walk every column / model against `gap_catalog` triggers:\n\n- For each column matching catalog #1 / #2 / #3 / #5 / #7 triggers → is the corresponding `[tag]` line present in `properties.description`?\n- For each model with a soft-delete column (`deleted_at`, `is_active`, `archived_at`, etc.) → is there a `## Default filters` rule in `knowledge/rules/` covering it (catalog #4)?\n- For each lookalike table pair (e.g. `users` / `users_v3`) → is there a `## Canonical tables` rule (catalog #10)?\n- For each `*_currency` / `fx_rate` / external-system ID column → is the matching `## Currency` (#9) or `## External identifiers` (#8) section present?\n- Business terms in `knowledge/rules/` or raw that don't map verbatim to model / column names → catalog #6 `## Naming conventions` rule missing.\n\nEach unsatisfied check is a candidate. Combine with Step 4.5 probe results (if available) before moving to Lane 2.\n\n### Lane 2 — Claim-diff (raw vs current context)\n\nFor each raw file, internally extract 5–15 **atomic claims** — single statements that could be true or false, e.g. \"an order has exactly one customer\", \"user means type=default by default\", \"ARR equals MRR × 12 minus refunds\". Then for each claim, classify against the current Wren context:\n\n| Class | Meaning | Resolution outcome |\n|---|---|---|\n| **covered** | already reflected in MDL / instructions / pairs | skip |\n| **partial** | the topic exists but the wording / scope differs | propose tightening |\n| **new** | nothing in current context matches | route to a sink |\n| **conflict** | raw says A, current context says B | grill the user (both modes), **but do not edit existing** — surface for manual fix |\n\n### Lane 3 — Inference (your own guesses)\n\nAfter reading raw and the current MDL, propose additions the user did **not** literally state in raw but that would clearly help the agent later. Examples:\n\n- \"I see `quarterly_churn` referenced five times in `finance.pdf`. No existing cube covers it. Want me to add `cubes/quarterly_churn/metadata.yml` with measure = `COUNT(*) FILTER (WHERE churned_at IS NOT NULL) / NULLIF(COUNT(*), 0)`?\" — see `cube_proposals` for the YAML template and duplication guard.\n- \"Your support handbook keeps mentioning `core users` without defining it. Is this `users WHERE tier = 'premium'`? Want me to make a view?\"\n- \"The data dictionary says `events.payload` is JSON but the column has no description — let me draft one.\"\n\nFor any aggregation-shaped proposal (`SUM`, `COUNT`, `AVG`, \"by month / by status / per customer\" patterns), **default to a cube**. Run `wren cube list` + `wren cube describe` first to confirm no existing cube already covers the measure expression; if one does, skip the proposal and add a `knowledge/sql/` example pointing at the existing cube instead. The full decision tree, naming rules, and validation flow live in `cube_proposals`.\n\n**In grill mode, open every Lane 3 question with \"I'm guessing — \".** In auto-pilot, tag the audit entry with `agent inference` so the user sees you extrapolated.\n\n## Step 6 — Resolve gaps\n\nBranch on the `MODE` locked in Step 0.\n\n### Grill mode\n\nUse this conversational pattern for every gap surfaced in Lanes 1–3:\n\n> Interview the user relentlessly about every gap until we reach a shared understanding. Walk down each branch of the decision tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.\n>\n> Ask the questions one at a time.\n>\n> If a question can be answered by exploring the codebase or the raw files, do that instead of asking.\n\nFor every grill turn:\n\n1. State the gap and where it came from (Lane 1 / 2 / 3, and for Lane 2 quote the raw file + a short excerpt).\n2. Propose the concrete answer — draft the description, the rule, the SQL pair, the relationship.\n3. **Propose the sink** (\"I'll add this to `knowledge/rules/` as a rule\" / \"I'll add this to the `users` model description in MDL\").\n4. Let the user accept / edit / skip.\n5. On accept: write back (Step 7).\n6. On edit: apply their wording, then write back.\n7. On skip: drop it, move to the next gap. Do not requeue.\n\nWhen the user gives a curve-ball answer (\"actually we don't track that\") — pivot. The goal is shared understanding, not pushing a pre-built list.\n\n### Auto-pilot mode\n\nProcess every finding from Lanes 1–3 directly — **except** for the three escalation cases from Universal Rule 7:\n\n- **Lane 2 conflict** — drop into grill for this single question, never auto-resolve.\n- **Lane 3 proposing a new metric / view / relationship** — drop into grill before applying.\n- **Lane 2 routing ambiguous** — drop into grill for the sink choice only, then auto-apply.\n\nFor everything else (Lane 1 mechanical fixes, Lane 2 unambiguous new claims, Lane 3 low-impact description tweaks):\n\n1. Synthesize the concrete proposal (description / rule / SQL pair) using your best inference.\n2. Decide the sink using the routing table (Step 7).\n3. Write back.\n4. Run `wren context validate` immediately after any MDL edit. On failure: revert the single change and log the revert with the error.\n5. Append to the audit list: the finding, the sink, confidence tag (high / med / low), and source (raw file + excerpt for Lane 2, `agent inference` for Lane 3, `structural` for Lane 1).\n\nAuto-pilot does not pause for confirmation on each item — the user reviews the full diff + audit list in Step 9. They are the reviewer, not the gatekeeper.\n\n## Step 7 — Routing & writeback\n\nDecide the sink as part of the proposal (Step 6.3 in grill mode; Step 6.2 in auto-pilot), so the user can correct routing in grill mode and audit it in auto-pilot.\n\n| Finding type | Sink | How to write |\n|---|---|---|\n| Schema structure / relationship / view / model or column description | **MDL YAML** under `models/`, `views/`, `relationships.yml` | Edit the YAML file directly. For catalog #1 / #2 / #3 / #5 / #7 / PII, append a `[tag]` line to `properties.description` (prose first, then one tag per category). See `gap_catalog` for the exact tag format and triggers. |\n| Aggregation metric / named measure (with measures + dimensions) | **`cubes/<name>/metadata.yml`** | New file per cube. Default sink for any `SUM` / `COUNT` / `AVG` / ratio metric raw defines or Lane 3 infers. See `cube_proposals` for the YAML template, naming policy, duplication guard, and validation flow. Run `wren context validate` + `wren cube query --cube <name> --sql-only` after writing; revert on either failure. **Always escalates to grill in auto-pilot** (Universal Rule 7b). |\n| Default filter / implicit rule / business convention / naming convention / external mapping / currency / canonical table | **`knowledge/rules/`** | Append under the catalog-specified `##` section heading (#4 → `## Default filters`, #6 → `## Naming conventions`, #8 → `## External identifiers`, #9 → `## Currency`, #10 → `## Canonical tables`) inside a topic file under `knowledge/rules/` (e.g. `knowledge/rules/conventions.md`). Create the file/heading if absent; never modify existing text. |\n| NL→SQL example (canonical or ad-hoc) | **`knowledge/sql/`** | `wren memory store --nl \"...\" --sql \"...\" --tags \"source:enrich\"` — writes `knowledge/sql/<slug>.md` (committable) and indexes it when the extra is present. Works whether or not `MEMORY_AVAILABLE`. |\n\nCatalog-driven routing means every column-local proposal goes to the column's `properties.description` with a `[tag]` line; every cross-model rule goes to `knowledge/rules/` under a fixed heading. This keeps re-enrichment deterministic (greppable) and avoids inventing new sink locations.\n\n### After every MDL edit\n\n```bash\nwren context validate\n```\n\nIf it fails:\n1. Revert the single change you just made.\n2. Show the user the validation error (grill mode) or log it in the audit (auto-pilot).\n3. In grill mode, re-grill on that specific gap with the error as new context.\n4. In auto-pilot, mark the finding as \"revert: validation failed\" and move on.\n\n### Format reminders\n\n- NL→SQL pairs are written by `wren memory store`; each becomes a `knowledge/sql/<slug>.md` with YAML frontmatter (`nl`, `sql`, `source`, optional `datasource`/`tags`). Don't hand-write the files — use `wren memory store --tags \"source:enrich\"`.\n- MDL YAML uses snake_case keys (e.g. `primary_key`, `is_calculated`, `not_null`). `wren context build` converts to camelCase for `target/mdl.json`.\n- `knowledge/rules/` holds free-form markdown, one file per topic. Group rules by topic with `##` headings.\n\n## Step 8 — Session finalize\n\nAfter Step 6 ends (user says stop in grill mode, or every finding is processed in auto-pilot):\n\n```bash\nwren context build\n```\n\nThis recompiles `target/mdl.json` from the YAML edits.\n\nIf `MEMORY_AVAILABLE = true`:\n\n```bash\nwren memory index\n```\n\nThis rebuilds the index from the MDL schema items and the `knowledge/sql/` pairs. (Business\nrules in `knowledge/rules/` are read by `wren context instructions`, not embedded by `index`.)\n\n## Step 9 — Summary\n\n### Both modes — common section\n\nPrint a tight session report:\n\n```text\nWren Enrich Context — session summary  (mode: <grill|autopilot>)\n\nAdded:\n  MDL              : N model descriptions, N column descriptions, N relationships, N views\n                     by tag: [enum]=N [unit]=N [null]=N [magic]=N [time]=N [pii]=N\n  cubes            : N new (names: <list>)                                    via cubes/<name>/metadata.yml\n  knowledge/rules/ : N new rules across sections\n                     by section: Default filters=N | Naming conventions=N | External identifiers=N | Currency=N | Canonical tables=N\n  knowledge/sql/   : N new NL→SQL pairs                                       via wren memory store\n  Probe            : N columns sampled, M failed                             (grill mode only)\n\nPlease fix manually (we don't edit existing fields):\n  - models/orders/metadata.yml: existing description seems to contradict raw/glossary.pdf p.3\n  - relationships.yml: existing orders↔customers is MANY_TO_ONE but raw/data_dict.md p.7 says MANY_TO_MANY\n  - …\n```\n\n### Grill mode extras\n\nAppend:\n\n```text\nSkipped this session: N gaps (re-run /wren-enrich-context to revisit)\n```\n\n### Auto-pilot mode extras\n\nAppend a detailed audit so the user can sanity-check inferences:\n\n```text\nInferred items (please review):\n  high   | MDL model:orders.description            | from raw/glossary.pdf p.2 — \"Order = ...\"\n  high   | knowledge/rules/ rule                   | from raw/handbook.md §4 — \"default tier ...\"\n  med    | MDL column:users.signup_source.desc     | agent inference from raw/onboarding.md\n  low    | knowledge/sql/: \"weekly active customers\" | agent inference, no direct raw evidence\n\nValidation:\n  K successful applies, M reverted after wren context validate failed:\n    - relationships.yml: <error> → reverted\n\nEscalated to grill (raw vs MDL conflicts / high-impact additions):\n  - <count> items — see grill transcript above\n```\n\nThe user should be encouraged to skim the audit and either accept it as-is, manually tweak low-confidence rows, or re-run in grill mode if they want to revisit interactively.\n\n## Things to avoid\n\n- Do not write a `gaps.yml`, `state.yml`, or any other tracking artifact. The session lives entirely in conversation.\n- Do not modify any existing MDL field, `knowledge/rules/` rule, or `knowledge/sql/` pair — only append / add. Surface mismatches on the manual-fix list.\n- Do not install new Python packages (`pypdf`, `docling`, …) to read raw. Use what your agent already has; ask the user to convert files you can't open.\n- Do not auto-resolve a conflict between raw and current MDL — always grill the user, in **both** modes.\n- Do not present Lane 3 inferences as if they were quoted from raw. Open with \"I'm guessing — \" (grill) or tag `agent inference` (auto-pilot).\n- `wren memory store` works whether or not `MEMORY_AVAILABLE` — it always writes the `knowledge/sql/*.md` pair (and indexes it only when the extra is present). No need to fall back to another sink.\n- Do not commit anything to git. The user owns the commit decision.\n- Do not nag about skipped questions. Skip is skip for this session (grill mode only — auto-pilot has no skip concept).\n- Do not run `wren context build` after every single MDL edit — once at the end is enough. Do run `wren context validate` after every edit.\n- Do not assume `raw/` was created by `wren context init` — it isn't. This skill creates it.\n- Do not switch modes mid-session. The user re-runs to change mode.\n- Do not append a `[tag]` line if the same category tag already exists for that column — Universal Rule 1. Surface contradictions on the manual-fix list instead.\n- Do not invent new `knowledge/rules/` section headings. Stick to the five catalog-defined headings (`## Default filters`, `## Naming conventions`, `## External identifiers`, `## Currency`, `## Canonical tables`). Anything that doesn't fit goes on the manual-fix list.\n- Do not probe the live DB in auto-pilot mode. Step 4.5 is grill-only by default.\n- Do not propose a cube whose measure expression already exists in another cube on the same `base_object` — write a `knowledge/sql/` example pointing at the existing cube instead. See `cube_proposals` duplication guard.\n- Do not modify an existing cube YAML even when raw contradicts it — Universal Rule 1. Surface on the manual-fix list.\n- Do not write a new cube alongside an old MDL `metrics:` entry that already covers the same logic. Surface as \"consider migrating to cube\" on the manual-fix list.\n- Do not skip `wren cube query --cube <name> --sql-only` after creating a cube. Structural `wren context validate` doesn't catch unresolvable measure / dimension expressions.\n- In auto-pilot, do not auto-apply Lane 2 conflicts or new metric / view / relationship inferences — always drop into grill for those.\n\n## See also\n\n- `gap_catalog` — the ten business-semantic gap categories, with triggers, default sinks, and write formats. Read this before Step 4.5 and Step 5.\n- `cube_proposals` — decision tree for when to propose a cube vs view vs calculated column, the cube YAML template, naming policy, duplication guard, and validation flow. Read this before any Lane 3 aggregation-shaped proposal.\n","core/wren/src/wren/skills_content/genbi/SKILL.md":"---\nname: genbi\ndescription: \"Turn a Wren project's context layer into a shareable, browser-side GenBI web app and deploy it to the user's Vercel or Cloudflare account. Orchestrates the full flow: `wren genbi build` returns a project-hydrated build instruction, the agent authors the app from scratch into apps/<name>/, then register → verify → deploy produce a shareable URL. Use this skill whenever the user wants to: build a dashboard from their Wren project, make a shareable analytics app, deploy their context layer as a web app, host a GenBI app on Vercel or Cloudflare Pages, or asks for a 'genbi app'.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren GenBI App — Agent Workflow Guide\n\n> This guide is served by the `wren` CLI (`wren skills get genbi`), so it\n> always matches your installed wrenai version.\n\nTurn a Wren context layer into a shareable GenBI app — from a natural-language\nrequest to a public URL in one conversation.\n\n**Division of labor:** the CLI owns the authoritative build instruction (it\nknows the live project facts and the pinned `wren-core-wasm` version) and all\ndeterministic state (index, verify, deploy). You — the agent — author the app\ncode by following the instruction. Never hand-write `.wren/apps.yml`.\n\n## Preconditions\n\n1. A Wren project is discoverable (`wren_project.yml` in cwd/ancestors, or ask\n   for the path and pass `-p`).\n2. The context layer exists. If `target/mdl.json` is missing, `wren genbi\n   build` compiles it implicitly — no separate step needed.\n3. `wren` CLI ≥ the version that ships the `genbi` command group\n   (`wren genbi --help` works).\n\n## Workflow\n\n### 1. Resolve the app name and data mode\n\n- App name: short kebab-case derived from the request (e.g. `sales-overview`).\n- Data mode:\n  - `snapshot` (default) — data is bundled with the app as parquet/duckdb and\n    queried client-side. Fully serverless. Right for demos, reports, small\n    data, and dlt-pipeline output.\n  - `live` — the app calls back to the user's warehouse/API at view time.\n    Right for production-scale or always-fresh data. Requires a CORS-enabled\n    endpoint and carries strict no-credentials rules.\n- Ask the user ONLY if the choice is genuinely ambiguous.\n\n### 2. Get the build instruction\n\n```bash\nwren genbi build <name> --prompt \"<the user's request, verbatim>\" --data-mode <mode>\n```\n\nFor long or multi-line prompts use `--prompt-file <file>` or pipe to\n`--prompt -`. The command prints the authoritative build instruction —\nwasm wiring (pinned version, CDN load), the project's model/column inventory,\ndata-mode guidance, acceptance criteria, and the target folder. It writes no\napp files — the only thing it may touch is `target/mdl.json`, which it compiles\nfirst if missing (see Precondition 2).\n\n### 3. Author the app\n\nFollow the instruction exactly. Key conventions:\n\n- Write everything under `apps/<name>/` — never outside it.\n- Copy the compiled MDL into the app as `apps/<name>/mdl.json`.\n- Load `wren-core-wasm` from the CDN given in the instruction; never bundle\n  the ~68MB binary.\n- snapshot: export the data the dashboard needs into `apps/<name>/data/` as\n  parquet (`verify` requires at least one `.parquet`/`.duckdb` asset). See\n  **Snapshot data export** below for the recipe and where the data comes from.\n- live: write an endpoint-only connection config. NEVER inline credentials —\n  `verify` scans for them (best-effort) and `deploy` gates on `verify`, but\n  the rule is on you: a public static host exposes every shipped file.\n- Design the dashboard to actually answer the user's request: pick the right\n  charts/tables for the question, not a generic template.\n\n#### Snapshot data export\n\nThe CLI hands you the build instruction, but the snapshot bytes still have to\nbe exported into the app folder — that step is yours.\n\n**Where the data comes from:**\n\n- **DuckDB-backed project** (incl. anything loaded by the `dlt-connector`\n  skill — its pipelines always land in a `.duckdb` file): the project's DuckDB\n  file *is* your snapshot source. If the user is connecting SaaS data (HubSpot,\n  Stripe, Salesforce, …) and has no project yet, run the SaaS→project flow\n  first: `wren skills get dlt-connector`. Then come back here to ship it.\n- **Warehouse-backed project** (Postgres, BigQuery, Snowflake, …): run the\n  query/queries the dashboard needs through the MDL layer and write the result\n  to parquet. Keep snapshots small — snapshot mode is for demos/reports, not\n  full warehouse extracts; use `live` mode for large or always-fresh data.\n\n**Recipe (DuckDB → parquet):**\n\n```bash\n# from the project root; <db> is the project's DuckDB file (see wren_project.yml)\npython - <<'PY'\nimport duckdb\ncon = duckdb.connect(\"<db>.duckdb\", read_only=True)\ncon.execute(\n    \"COPY (SELECT * FROM <table>) \"\n    \"TO 'apps/<name>/data/<table>.parquet' (FORMAT parquet)\"\n)\nPY\n```\n\nOnly export the columns/rows the dashboard uses. The compiled `mdl.json` you\ncopied in keeps the context layer intact regardless of how you bundle data.\n\n### 4. Register and verify\n\n```bash\nwren genbi register <name> --data-mode <mode>\nwren genbi verify <name>\n```\n\nIf verify fails: fix the reported problems and re-run verify. Do NOT proceed\nto deploy on a failed verify. Offer `wren genbi open <name>` for a local\npreview before shipping.\n\n### 5. Deploy (only if the user asked for it)\n\n```bash\nwren genbi deploy <name> --provider vercel      # or cloudflare\n```\n\n- Preview deployment by default. **Confirm with the user before `--prod`.**\n- Tokens: the CLI discovers `VERCEL_TOKEN` / `CLOUDFLARE_API_TOKEN` from the\n  environment or `.env` files. If missing, ask the user to export it or add\n  it to the project `.env`. NEVER put a token on the command line.\n- Cloudflare also needs `CLOUDFLARE_ACCOUNT_ID` (env or `.env`), a token\n  scoped with Pages:Edit, and the `wrangler` CLI on PATH (or `npx` available)\n  — the adapter shells out to `wrangler pages deploy`. If it's missing, ask\n  the user to `npm install -g wrangler`.\n- Report the returned URL to the user. Re-deploying the same app updates the\n  same provider target.\n- **Verify the URL actually loads.** After deploying, fetch the URL — a\n  successful deploy can still return **HTTP 401/403** to outsiders because of\n  the provider's access protection (see below). Don't report a link as\n  \"shareable\" until you've confirmed it serves.\n\n### Vercel Deployment Protection (the 401 trap)\n\nNew Vercel projects ship with **Vercel Authentication** turned ON by default,\nso every deployment — preview *and* production — returns **401** to anyone not\nlogged into the owning Vercel account/team. The deploy itself succeeded; the\nURL is just gated.\n\n- To make the app publicly shareable, the user disables it in the Vercel\n  dashboard: **Project → Settings → Deployment Protection → Vercel\n  Authentication → Disabled** (or scope it to production only). This setting\n  is not controllable from `wren genbi deploy`; it's a one-time toggle per\n  project in Vercel.\n- If the user only needs a private link (viewable while logged into their\n  Vercel account), leaving protection on is fine — just tell them the link\n  won't work for logged-out visitors.\n\n## Safety boundaries\n\n- Never inline secrets/credentials into app files — the deploy target is a\n  public static host; anyone with the URL can read every file. `verify`\n  scans for inlined credentials, but treat it as best-effort\n  defense-in-depth, not a guarantee — never rely on it to catch a secret\n  you shouldn't have written in the first place.\n- Never pass tokens as CLI flags; they leak into shell history.\n- Confirm before production deploys.\n- All index state goes through `wren genbi register/remove` — never edit\n  `.wren/apps.yml` by hand.\n\n## Quick reference\n\n| Step | Command |\n| --- | --- |\n| Get build instruction | `wren genbi build <name> --prompt \"…\" [--data-mode snapshot\\|live]` |\n| Record the app | `wren genbi register <name> --data-mode <mode>` |\n| Preflight | `wren genbi verify <name>` |\n| Local preview | `wren genbi open <name>` |\n| Ship | `wren genbi deploy <name> --provider vercel\\|cloudflare [--prod]` |\n| Inventory | `wren genbi list` / `wren genbi remove <name>` |\n","core/wren/src/wren/skills_content/generate-mdl/SKILL.md":"---\nname: generate-mdl\ndescription: \"Generate a Wren MDL project by exploring a database with available tools (SQLAlchemy, database drivers, MCP connectors, or raw SQL). Guides agents through schema discovery, type normalization, and MDL YAML generation using the wren CLI. Use when: user wants to create or set up a new MDL, onboard a new data source, or scaffold a project from an existing database.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Generate Wren MDL — CLI Agent Workflow\n\nBuilds an MDL project by discovering database schema and converting it\ninto Wren's YAML project format. The agent uses whatever database tools\nare available in its environment for introspection; the wren CLI handles\ntype normalization, validation, and build.\n\nFor memory and query workflows after setup, run `wren skills get usage`.\n\n## Prerequisites\n\n- `wren` CLI installed (`pip install \"wrenai[<datasource>]\"`)\n- A working database connection (credentials available to the agent)\n- A connection profile (set up via `wren profile add`) or connection info ready\n\n## Alternative — import from an OpenMetadata catalog\n\nIf the user's team already catalogs this database in OpenMetadata, import\nfrom the catalog instead of introspecting the live database. The import\ncarries curated table/column descriptions, tags, owners, and foreign-key\nconstraints (as `relationships.yml`), so the join-inference work in Phase 4\nis mostly done:\n\n```bash\nwren context import openmetadata --host <om_url> --token <om_token> \\\n  --service <om_service> --database <db> --schema <schema> --dry-run\nwren context import openmetadata --host <om_url> --token <om_token> \\\n  --service <om_service> --database <db> --schema <schema>\n```\n\nAsk the user for the OpenMetadata URL, a read-only bot or personal access\ntoken, and the service name as it appears in OpenMetadata. `--table`\n(repeatable) narrows the import. Without `--force` the command only adds\nfiles that do not exist and lists the collisions it left untouched; `--force`\nregenerates from OpenMetadata and discards local edits to generated files —\nconfirm with the user before using it. Relationships come from foreign keys\nonly; review whether additional joins are needed (Phase 4), then bind a\nprofile and finish with Phase 5 (`wren context validate` → `wren context build`).\n\n## Phase 0 — Detect existing project\n\n**Goal:** If the current directory is already inside a wren project, let the user decide how to proceed.\n\nCheck whether `wren_project.yml` exists in the current working directory\n(or any parent up to the repository root). If found:\n\n1. Tell the user that an existing wren project was detected and show its path.\n2. Ask:\n   - **Reset** — wipe the existing project (`models/`, `views/`,\n     `relationships.yml`, `knowledge/`, and rebuild `wren_project.yml`)\n     and regenerate from scratch in the same directory.\n   - **New path** — keep the existing project untouched and choose a\n     different directory for the new project. Ask the user for the new path,\n     then `wren context init --path <new_path>` and continue from Phase 1\n     using that path.\n\nIf no existing project is detected, proceed directly to Phase 1.\n\n## Phase 1 — Establish connection and scope\n\n**Goal:** Confirm the agent can reach the database and agree on scope with the user.\n\n1. Verify connectivity using whichever tool is available:\n   - If SQLAlchemy: `engine.connect()` test\n   - If database driver: simple query like `SELECT 1`\n   - If wren profile exists: `wren profile debug` to check config\n   - If raw SQL via wren: `wren --sql \"SELECT 1\"` (requires profile or connection file)\n\n2. Ask the user:\n   - Which **schema(s)** or **dataset(s)** to include (skip if only one exists)\n   - Whether to include **all tables** or a subset\n   - The **datasource type** for wren (e.g., `postgres` (including Aurora), `mysql` (including Aurora), `bigquery`, `snowflake`) — needed for type normalization dialect\n\n## Phase 2 — Discover schema\n\n**Goal:** Collect table names, column names, column types, and constraints.\n\nUse whatever introspection method is available. Here are common approaches\nranked by convenience:\n\n### Option A: SQLAlchemy (recommended if available)\n\n```python\nfrom sqlalchemy import create_engine, inspect\n\nengine = create_engine(connection_url)\ninspector = inspect(engine)\n\ntables = inspector.get_table_names(schema=\"public\")\n\nfor table in tables:\n    columns = inspector.get_columns(table, schema=\"public\")\n    # columns → [{\"name\": \"id\", \"type\": INTEGER(), \"nullable\": False, ...}]\n\n    pk = inspector.get_pk_constraint(table, schema=\"public\")\n    # pk → {\"constrained_columns\": [\"id\"], \"name\": \"orders_pkey\"}\n\n    fks = inspector.get_foreign_keys(table, schema=\"public\")\n    # fks → [{\"constrained_columns\": [\"customer_id\"],\n    #          \"referred_table\": \"customers\",\n    #          \"referred_columns\": [\"id\"]}]\n```\n\n### Option B: Database-specific driver\n\n- **psycopg / asyncpg (Postgres):** Query `information_schema.columns` and `information_schema.table_constraints`\n- **google-cloud-bigquery:** `client.list_tables()`, `client.get_table()` → `table.schema`\n- **snowflake-connector-python:** `SHOW COLUMNS IN TABLE`, `SHOW PRIMARY KEYS IN TABLE`\n- **clickhouse-driver:** `DESCRIBE TABLE`, `system.tables`\n\n### Option C: Raw SQL via wren\n\nIf no driver is available but a wren profile is configured, query\n`information_schema` through wren itself:\n\n```bash\nwren --sql \"SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'\" -o json\nwren --sql \"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'orders'\" -o json\n```\n\nNote: this goes through the MDL layer, so it only works if you already\nhave a minimal MDL or if the database supports `information_schema` as\nregular tables. For bootstrapping from zero, Option A or B is preferred.\n\n## Phase 3 — Normalize types\n\n**Goal:** Convert raw database types to wren-core-compatible types.\n\n### Python import (recommended for batch processing)\n\n```python\nfrom wren.type_mapping import parse_type, parse_types\n\n# Single type\nnormalized = parse_type(\"character varying(255)\", \"postgres\")  # → \"VARCHAR(255)\"\n\n# Batch — entire table at once\ncolumns = [\n    {\"column\": \"id\", \"raw_type\": \"int8\"},\n    {\"column\": \"name\", \"raw_type\": \"character varying\"},\n    {\"column\": \"total\", \"raw_type\": \"numeric(10,2)\"},\n]\nnormalized_cols = parse_types(columns, dialect=\"postgres\")\n# Each dict now has a \"type\" key with the normalized value\n```\n\n### CLI (if Python import not available)\n\nSingle type:\n```bash\nwren utils parse-type --type \"character varying(255)\" --dialect postgres\n# → VARCHAR(255)\n```\n\nBatch (stdin JSON):\n```bash\necho '[{\"column\":\"id\",\"raw_type\":\"int8\"},{\"column\":\"name\",\"raw_type\":\"character varying\"}]' \\\n  | wren utils parse-types --dialect postgres\n```\n\n## Phase 4 — Scaffold and write MDL project\n\n**Goal:** Create the YAML project structure.\n\n### Step 1 — Initialize project\n\n```bash\nwren context init --path /path/to/project\n```\n\nThis creates:\n```text\nproject/\n├── wren_project.yml\n├── models/              # business-facing tables/models\n├── views/               # named SQL statements\n├── cubes/               # pre-aggregation cubes (measures + dimensions)\n├── relationships.yml\n└── knowledge/           # business rules (rules/) + NL→SQL pairs (sql/)\n```\n\n> **When to define cubes:** If the user asks aggregation questions like\n> \"revenue by month\" or \"top customers\", define cubes alongside models —\n> they give agents a structured query API instead of forcing them to\n> hand-write `GROUP BY` / `DATE_TRUNC` SQL. See the\n> [Cube guide](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/cubes.md).\n\n> **IMPORTANT: `catalog` and `schema` in `wren_project.yml`**\n>\n> These are Wren Engine's internal namespace — they are NOT the database's\n> native catalog or schema. Keep the defaults (`catalog: wren`, `schema: public`)\n> unless you are intentionally configuring a multi-project namespace.\n>\n> Your database's actual catalog/schema is specified per-model in `table_reference`\n> (see Step 2). Do not copy database catalog/schema values into `wren_project.yml`.\n\n### Step 2 — Write model files\n\nFor each table, create a YAML file under `models/`. Use snake_case\nnaming (the build step converts to camelCase automatically).\n\n```yaml\n# models/orders/metadata.yml\nname: orders\ntable_reference:\n  catalog: \"\"           # database catalog (empty string if not applicable;\n                        #   for DuckDB, use the DB file name without extension,\n                        #   e.g. jaffle_shop.duckdb → catalog: jaffle_shop)\n  schema: public        # database schema (this IS the DB schema)\n  table: orders         # database table name\nprimary_key: order_id\ncolumns:\n  - name: order_id\n    type: INTEGER\n    not_null: true\n  - name: customer_id\n    type: INTEGER\n  - name: total\n    type: \"DECIMAL(10, 2)\"\n  - name: status\n    type: VARCHAR\n    properties:\n      description: \"Order status: pending, shipped, delivered, cancelled\"\n```\n\n### Step 3 — Write relationships\n\nFrom foreign key constraints discovered in Phase 2:\n\n```yaml\n# relationships.yml\nrelationships:\n  - name: orders_customers\n    models:\n      - orders\n      - customers\n    join_type: many_to_one\n    condition: \"orders.customer_id = customers.customer_id\"\n```\n\nJoin type mapping:\n- FK table → PK table: `many_to_one`\n- PK table → FK table: `one_to_many`\n- Unique FK: `one_to_one`\n- Junction table: `many_to_many`\n\nIf no foreign keys were found, infer from naming conventions:\n- Column `<table>_id` or `<table_singular>_id` → likely FK to `<table>`\n- Ask the user to confirm inferred relationships\n\n### Step 4 — Add descriptions (optional but valuable)\n\nAsk the user to describe:\n- Each model (1-2 sentences about what the table represents)\n- Key columns (especially calculated fields or non-obvious names)\n\nThese descriptions are indexed by `wren memory index` and significantly\nimprove LLM query accuracy.\n\n## Phase 5 — Validate and build\n\n```bash\n# Validate YAML structure and integrity\nwren context validate --path /path/to/project\n\n# If strict mode is desired:\nwren context validate --path /path/to/project --strict\n\n# Build JSON manifest\nwren context build --path /path/to/project\n\n# Verify against database\nwren --sql \"SELECT * FROM <model_name> LIMIT 1\"\n```\n\nIf validation fails, fix the reported issues and re-run. Common errors:\n- Duplicate model/column names\n- Missing primary key\n- Relationship referencing non-existent model\n- Invalid column type (try re-running through `parse_type`)\n\n## Phase 6 — Initialize memory\n\n```bash\n# Index schema (generates seed NL-SQL examples automatically)\nwren memory index\n\n# Verify\nwren memory status\n```\n\nAfter this step, `wren memory fetch` and `wren memory recall` are\noperational. See `wren skills get usage` for query workflows.\n\n## Phase 7 — Iterate with the user\n\nThe initial MDL is a starting point. Improve it by:\n- Adding calculated columns based on business logic\n- Adding views for common query patterns\n- Refining descriptions based on actual query usage\n- Adding access control (RLAC/CLAC) if needed\n\nEach change follows: edit YAML → `wren context validate` →\n`wren context build` → `wren memory index`.\n\n## Quick reference\n\n| Task | Command / Method |\n|------|-----------------|\n| Discover tables | Agent's own tools (SQLAlchemy, driver, raw SQL) |\n| Discover columns + types | Agent's own tools |\n| Discover constraints | Agent's own tools |\n| Normalize types (Python) | `from wren.type_mapping import parse_type` |\n| Normalize types (CLI) | `wren utils parse-type --type T --dialect D` |\n| Normalize types (batch) | `wren utils parse-types --dialect D < columns.json` |\n| Scaffold project | `wren context init` |\n| Write models | Create `models/<name>/metadata.yml` |\n| Write relationships | Edit `relationships.yml` |\n| Validate | `wren context validate` |\n| Build manifest | `wren context build` |\n| Test query | `wren --sql \"SELECT * FROM <model> LIMIT 1\"` |\n| Index memory | `wren memory index` |\n\n## Things to avoid\n\n- Do not hardcode database-specific type strings in MDL — always normalize via `parse_type`\n- Do not skip validation before build — invalid YAML produces broken manifests silently\n- Do not guess column types — introspect from the actual database\n- Do not write relationships without confirming join conditions — wrong conditions cause silent query errors\n- Do not skip `wren memory index` after build — stale indexes degrade recall quality\n","core/wren/src/wren/skills_content/onboarding/SKILL.md":"---\nname: onboarding\ndescription: \"Onboard a user to Wren Engine end-to-end. Walks through environment checks, project scaffolding, connection configuration via .env, and first query. Use when: user wants to install Wren Engine, set up a new data source connection, or bootstrap a new project from scratch. Triggers: '/wren-onboarding', 'install wren', 'set up wren engine', 'wren onboarding', 'connect new database to wren'.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren Onboarding — Agent Workflow\n\nThis skill walks the agent through onboarding — environment checks, project scaffolding, profile creation, MDL generation, and first query. **Procedural details, per-datasource setup notes, and the troubleshooting playbook live in the docs**, not here. The skill's job is to enforce the agent-side rules (one step per turn, never ask for credentials in chat) and to dispatch the agent to the right doc / sibling skill at each step.\n\nReference docs (the skill points to these — never duplicate their content):\n- [`docs/core/get_started/installation.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/get_started/installation.md) — CLI install + skill install\n- [`docs/core/guides/connect.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md) — full connection procedure, **per-datasource setup notes, complete troubleshooting playbook**\n- [`docs/core/get_started/quickstart.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/get_started/quickstart.md) — bundled `jaffle_shop` demo\n\n## Mode of operation — READ THIS FIRST\n\n**One step per round-trip.** Each numbered step below is its own turn: explain briefly, ask **only** what the step needs, run the command(s), confirm, move on.\n\n- ❌ **Never collect information for future steps upfront.** Do not ask for project name + database type + credentials in one message.\n- ❌ **Never ask for credentials in chat — not host, port, user, password, tokens, anything.** Credentials always go through `.env`. The user fills the file in their editor; the agent never sees the values.\n- ❌ **Never query the database before MDL is built** via `wren skills get generate-mdl`.\n- ❌ **Never invent connection field names.** Always run `wren docs connection-info <ds>` to see the real fields — it's introspected from the live Pydantic schema, so it's always correct.\n- ✅ Wait for each command to finish, report its output in plain language, then move on.\n- ✅ For any error, consult `connect.md#troubleshooting` and surface the relevant section to the user — don't carry a copy of the playbook here.\n\n## Preflight (environment only — no user questions about the project)\n\nRead-only checks. Report findings, do **not** ask about project / credentials / datasource yet.\n\n1. `python3 --version` — requires Python 3.11+. If older, ask the user to upgrade and stop.\n2. Check virtualenv: `python3 -c \"import sys; print(sys.prefix != sys.base_prefix)\"`. If `False`, offer to create one (`python3 -m venv .venv && source .venv/bin/activate`). PEP 668 systems will need this.\n3. `wren --version` — if already installed, confirm before reinstalling.\n4. `pwd` — record it. Don't ask where the project should live yet.\n\nReport findings as a 4-bullet list, then continue.\n\n## Early branch — demo or own database?\n\n> \"Try the bundled `jaffle_shop` demo first (~30s, no DB needed), or connect your own database?\"\n\n- **demo** → point at [`quickstart.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/get_started/quickstart.md) and stop this skill.\n- **own DB** → continue.\n\n## Step 1 — Collect project name + database type\n\nThese two are the only thing Step 2 needs; ask both together so the user has a clean handoff:\n\n> \"Two things before I scaffold:\n> 1. **Project name** — I'll create `~/<name>/` and `cd` into it.\n> 2. **Database type** — run `wren docs connection-info` (no argument) to see the full list, or pick a common one: `postgres` (use for Aurora PostgreSQL), `mysql` (use for Aurora MySQL), `bigquery`, `snowflake`, `clickhouse`, `trino`, `duckdb`, …\"\n\nWait for both. Don't ask for credentials.\n\n## Step 2 — Workspace + .env setup (batch)\n\nSide effects: creates `~/<project>/`, installs `wrenai[<ds>,main]`, generates an empty `.env` template. The project files (`wren_project.yml` etc.) come later in Step 3.5 — at this point we only have a directory with credentials waiting to be filled.\n\nRun as a batch — report each command briefly, then end with one \"please fill `.env`\" ask:\n\n1. `mkdir -p ~/<project> && cd ~/<project>`.\n2. `pip install \"wrenai[<ds>,main]\"`. For datasource-specific install gotchas (macOS mysql, etc.), see [`connect.md#per-datasource-setup-notes`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md).\n3. **Generate the `.env` template by introspecting the connector**:\n\n   ```bash\n   wren docs connection-info <ds> --format md\n   ```\n\n   Use the field list to write `.env` with `<DS>_<FIELD>=` keys (UPPER_SNAKE), values **empty**. Example for postgres:\n\n   ```ini\n   POSTGRES_HOST=\n   POSTGRES_PORT=5432\n   POSTGRES_DATABASE=\n   POSTGRES_USER=\n   POSTGRES_PASSWORD=\n   ```\n\n   Special encodings (BigQuery base64, Snowflake account format, Athena AWS creds, etc.) are documented in [`connect.md#per-datasource-setup-notes`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md). Surface the relevant section to the user verbatim — don't paraphrase.\n\n4. Add `.env` to `.gitignore` if the project is a git repo. Suggest `chmod 600 .env`.\n5. Tell the user: `.env` is at `<path>`, please fill every value and reply **\"done\"**.\n\n## Step 3 — Create the connection profile\n\nOnly after the user replies \"done\".\n\nWrite `/tmp/conn.yml` with **every field as a `${VAR}` placeholder** matching the `.env` keys you generated in Step 2:\n\n```yaml\ndatasource: <ds>\nhost: ${<DS>_HOST}\nport: ${<DS>_PORT}\n# … one line per field from `wren docs connection-info <ds>`\n```\n\nThen:\n\n```bash\nwren profile add <project> --from-file /tmp/conn.yml\n```\n\nValidation runs automatically. The CLI overwrites profiles silently — there is no `--force` flag.\n\n- ✓ **Success** → continue to Step 3.5.\n- ⚠ **Any warning** → consult [`connect.md#troubleshooting`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md) for the exact symptom (missing secret, driver auth failure, ValidationError, unreachable host, …) and tell the user what to fix.\n\n## Step 3.5 — Scaffold the project\n\n```bash\nwren context init --empty\n```\n\nRefuses to overwrite an existing `wren_project.yml`. Creates the project directory layout (`models/`, `views/`, `relationships.yml`, `knowledge/` (rules + sql), `AGENTS.md`).\n\n## Step 3.6 — Bind the profile to the project\n\n```bash\nwren context set-profile <project>\n```\n\nWrites both `profile: <project>` and `data_source: <ds>` into `wren_project.yml` (data_source is taken from the profile we just validated, so it's guaranteed correct). Future CLI commands and the SDK resolve the connection deterministically — independent of which profile is globally active.\n\nThis step also future-proofs the project for multi-project setups: once the binding is recorded, switching `wren profile switch` elsewhere never breaks this project's queries.\n\n## Step 4 — Generate MDL (hand off)\n\n> ⚠️ The agent **must** build MDL before any data query. Queries against tables not in MDL will fail.\n\nRun `wren skills get generate-mdl` and follow it. It walks the agent through table introspection, type normalization, and YAML generation. When it finishes, return here and run:\n\n```bash\nwren context validate\nwren context build\n```\n\nReport the model count and any validate warnings.\n\n**Memory recommendation**: count models with `wren context show | grep -c '^model:'`. If `>= 200`, suggest `pip install \"wrenai[memory]\"` + `wren memory index` (~800 MB). If `< 200`, skip.\n\n## Step 5 — Ready to explore (hand off)\n\nSuggest 2–3 NL questions based on the discovered tables (e.g. for an orders schema: \"How many orders last month?\", \"Top 5 customers by total\"). Then end this skill: for day-to-day querying the agent should run `wren skills get usage`.\n\n## Cross-skill routing\n\n| Trigger | Skill |\n|---------|-------|\n| User mentions a SaaS source (HubSpot, Stripe, Salesforce, GitHub, Slack, …) | `wren skills get dlt-connector` |\n| User has a connected DB but no MDL yet | `wren skills get generate-mdl` |\n| User has MDL ready, wants to query | `wren skills get usage` |\n| Anything else from-scratch | `wren skills get onboarding` (this skill) |\n\n## On error\n\nDon't carry an error playbook here — surface [`connect.md#troubleshooting`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md) sections to the user. The doc covers:\n\n- `wren: command not found`\n- `pip install … externally-managed-environment`\n- Missing secret (`MissingSecretError`)\n- Driver authentication failures\n- Pydantic `ValidationError` / unknown datasource\n- Connection refused / firewall / cloud DB IP allow-list\n- `wren context validate` warning categories\n\nIf you hit something not in the playbook, tell the user:\n\n> \"I hit an error I don't know how to fix: `<error>`.\n> See <https://docs.getwren.ai/oss/introduction> or open an issue at <https://github.com/Canner/WrenAI/issues>.\"\n","core/wren/src/wren/skills_content/usage/SKILL.md":"---\nname: usage\ndescription: \"Wren Engine CLI workflow guide for AI agents. Answer data questions end-to-end using the wren CLI: gather schema context, recall past queries, write SQL through the MDL semantic layer, execute, and learn from confirmed results. Use when: user asks a data question, requests a report or analysis, asks about metrics, revenue, customers, orders, trends, or any business data; user says 'how many', 'show me', 'what is the', 'top N', 'compare', 'trend', 'growth', 'breakdown'; user wants to explore, analyze, filter, aggregate, or summarize data from a database; agent needs to query data, connect a data source, handle errors, or manage MDL changes via the wren CLI.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren Engine CLI — Agent Workflow Guide\n\n> This guide is served by the `wren` CLI (`wren skills get usage`), so it always matches your installed wrenai version. Pull the deeper reference docs with `wren skills get usage --full`.\n\n## Preflight — Verify environment and installation\n\n**Goal:** Ensure the `wren` CLI is available before entering any workflow.\n\n### Step 1 — Check Python virtual environment\n\nRun `python -c \"import sys; print(sys.prefix)\"` (or equivalent) to determine\nwhether a virtual environment is active.\n\n- If **no venv is active**, warn the user and ask whether to:\n  - Create one (e.g., `python -m venv .venv && source .venv/bin/activate`)\n  - Continue without a venv (not recommended — may pollute global packages)\n\n### Step 2 — Check if the `wren` CLI is installed\n\nRun `wren --version`. If the command is not found or errors:\n\n1. Tell the user that the `wren` CLI is not installed.\n2. Ask if you should help install it.\n3. If the user agrees, determine the **datasource extra** to install:\n\n   **Auto-detect from project:** Check whether the current directory is inside\n   a wren project (look for `wren_project.yml` up to the repository root).\n   If found, read the active profile with `cat ~/.wren/profiles.yml` or look\n   for a datasource hint in the project's profile configuration. Extract the\n   datasource type from there.\n\n   **Ask the user:** If no project is detected or no datasource can be\n   inferred, ask the user which database they plan to connect to. Valid\n   extras: `postgres` (for Aurora Postgres), `mysql` (for Aurora MySQL), `bigquery`, `snowflake`, `clickhouse`,\n   `trino`, `mssql`, `databricks`, `redshift`, `spark`, `athena`, `oracle`.\n   DuckDB is included by default — no extra needed.\n\n4. Install with the detected or chosen extra:\n   ```bash\n   # DuckDB (no extra needed)\n   pip install \"wrenai\"\n\n   # Other datasources\n   pip install \"wrenai[<datasource>]\"\n   ```\n   To also enable semantic memory, interactive prompts, and web UI (recommended):\n   ```bash\n   pip install \"wrenai[<datasource>,main]\"\n   # or for DuckDB:\n   pip install \"wrenai[main]\"\n   ```\n\n5. Verify: `wren --version`\n\nIf `wren --version` succeeds, proceed to the relevant workflow below.\n\n---\n\nThe `wren` CLI queries databases through an MDL (Model Definition Language) semantic layer. You write SQL against model names, not raw tables. The engine translates to the target dialect.\n\nTwo things drive everything:\n- **Profile** — database connection + datasource type, managed via `wren profile` (stored in `~/.wren/profiles.yml`)\n- **Project** — MDL model definitions in YAML, compiled to `target/mdl.json` via `wren context build`\n\nThe CLI reads the active profile for connection info and datasource. Use `wren profile list` to see which profile is active, `wren profile switch <name>` to change it. `dry-plan` also accepts `--datasource` / `-d` for transpile-only use without a profile.\n\nFor memory-specific decisions, see the `memory` reference (run `wren skills get usage --full`).\nFor SQL syntax, CTE-based modeling, and error diagnosis, see the `wren-sql` reference (run `wren skills get usage --full`).\nFor project structure, MDL field definitions, and CLI workflow details, see the [documentation](https://github.com/Canner/WrenAI/tree/main/docs/core).\n\n---\n\n## Workflow 1: Answering a data question\n\n### Step 1 — Gather context\n\n| Situation | Command |\n|-----------|---------|\n| Default | `wren memory fetch -q \"<question>\"` |\n| Need specific model's columns | `wren memory fetch -q \"...\" --model <name> --threshold 0` |\n| Memory not installed | Read `target/mdl.json` in the project directory, or run `wren context show` |\n\nIf this is the first query in the conversation, also run:\n\n```text\nwren context instructions\n```\n\nIf it returns content, treat it as **rules that override defaults** — apply them to all subsequent queries in this session.\n\n### Step 2 — Recall past queries\n\n```bash\nwren memory recall -q \"<question>\" --limit 3\n```\n\nUse results as few-shot examples. Skip if empty.\n\n### Step 2.5 — Assess complexity (before writing SQL)\n\nIf the question involves **any** of the following, consider decomposing:\n- Multiple metrics or aggregations (e.g., \"churn rate AND expansion revenue\")\n- Multi-step calculations (e.g., \"month-over-month growth rate\")\n- Comparisons across segments (e.g., \"by plan tier, by region\")\n- Time-series analysis requiring baseline + change (e.g., \"retention curve\")\n\n**Decomposition strategy:**\n1. Identify the sub-questions (e.g., \"total subscribers at start\" + \"subscribers who cancelled\" → churn rate)\n2. For each sub-question:\n   - `wren memory recall -q \"<sub-question>\"` — check if a similar pattern exists\n   - Write and execute a simple SQL\n   - Note the result\n3. Combine sub-results to answer the original question\n\n**When NOT to decompose:**\n- Single-table aggregation with GROUP BY — just write the SQL\n- Simple JOINs that the MDL relationships already define\n- Questions where `memory recall` returns a near-exact match\n\nThis is a judgment call, not a rigid rule. If you're confident in a single\nquery, go ahead. Decompose when the SQL would be hard to debug if it fails.\n\n### Step 3 — Write, verify, and execute SQL\n\n**For simple queries** (single table or simple MDL-defined JOINs, straightforward aggregation):\nExecute directly:\n```bash\nwren --sql 'SELECT c_name, SUM(o_totalprice) FROM orders\nJOIN customer ON orders.o_custkey = customer.c_custkey\nGROUP BY 1 ORDER BY 2 DESC LIMIT 5'\n```\n\n**For complex queries** (non-trivial JOINs not covered by MDL relationships, subqueries, multi-step logic):\nVerify first with dry-plan:\n```bash\nwren dry-plan --sql 'SELECT ...'\n```\n\nCheck the expanded SQL output:\n- Are the correct models and columns referenced?\n- Do the JOINs match expected relationships?\n- Are CTEs expanded correctly?\n\nIf the expanded SQL looks wrong, fix before executing.\nIf it looks correct, proceed:\n```bash\nwren --sql 'SELECT ...'\n```\n\n**SQL rules:**\n- Target MDL model names, not database tables\n- Write dialect-neutral SQL — the engine translates\n\n### Step 4 — Store and continue\n\nAfter successful execution, **store the query by default**:\n\n```bash\nwren memory store --nl \"<user's original question>\" --sql \"<the SQL>\"\n```\n\n**Skip storing only when:**\n- The query failed or returned an error\n- The user said the result is wrong\n- The query is exploratory (`SELECT * ... LIMIT N` without analytical clauses)\n- There is no natural language question — just raw SQL\n- The user explicitly asked not to store\n\nThe CLI auto-detects exploratory queries — if you see no store hint\nafter execution, the query was classified as exploratory.\n\n| Outcome | Action |\n|---------|--------|\n| User confirms correct | Store |\n| User continues with follow-up | Store, then handle follow-up |\n| User says nothing (but question had clear NL description) | Store |\n| User says wrong | Do NOT store — fix the SQL |\n| Query error | See Error recovery below |\n\n---\n\n## Workflow 2: Error recovery\n\n### \"table not found\"\n\n1. Verify model name: `wren memory fetch -q \"<name>\" --type model --threshold 0`\n2. Check MDL exists: `ls target/mdl.json` (or `wren context show`)\n3. Verify column: `wren memory fetch -q \"<column>\" --model <name> --threshold 0`\n\n### Connection error\n\n1. Check active profile: `wren profile debug`\n2. Verify datasource and connection fields are correct\n3. Test: `wren --sql \"SELECT 1\"`\n4. Valid datasource values: `postgres` (for Aurora Postgres), `mysql` (for Aurora MySQL), `bigquery`, `snowflake`, `clickhouse`, `trino`, `mssql`, `databricks`, `redshift`, `spark`, `athena`, `oracle`, `duckdb`\n5. If no profile exists, create one: `wren profile add --ui` (or `--interactive` / `--from-file`)\n\n### SQL syntax / planning error (enhanced)\n\n#### Layer 1: Identify the failure point\n\n```bash\nwren dry-plan --sql \"<failed SQL>\"\n```\n\n| dry-plan result | Failure layer | Next step |\n|-----------------|---------------|-----------|\n| dry-plan fails | MDL / semantic | → Layer 2A |\n| dry-plan succeeds, execution fails | DB / dialect | → Layer 2B |\n\n#### Layer 2A: MDL-level diagnosis (dry-plan failed)\n\nThe dry-plan error message tells you exactly what's wrong:\n\n| Error pattern | Diagnosis | Fix |\n|---------------|-----------|-----|\n| `column 'X' not found in model 'Y'` | Wrong column name | `wren memory fetch -q \"X\" --model Y --threshold 0` to find correct name |\n| `model 'X' not found` | Wrong model name | `wren memory fetch -q \"X\" --type model --threshold 0` |\n| `ambiguous column 'X'` | Column exists in multiple models | Qualify with model name: `ModelName.column` |\n| Planning error with JOIN | Relationship not defined in MDL | Check available relationships in context |\n\n**Key principle**: Fix ONE issue at a time. Re-run dry-plan after each fix\nto see if new errors surface.\n\n#### Layer 2B: DB-level diagnosis (dry-plan OK, execution failed)\n\nThe DB error + dry-plan output together pinpoint the issue:\n\n1. Read the dry-plan expanded SQL — this is what actually runs on the DB\n2. Compare with the DB error message:\n\n| Error pattern | Diagnosis | Fix |\n|---------------|-----------|-----|\n| Type mismatch | Column type differs from assumed | Check column type in context, add explicit CAST |\n| Function not supported | Dialect-specific function | Use dialect-neutral alternative |\n| Permission denied | Table/schema access | Check connection credentials |\n| Timeout | Query too expensive | Simplify: reduce JOINs, add filters, LIMIT |\n\n**For small models**: If the error message is unclear, try simplifying\nthe query to the smallest failing fragment. Execute subqueries independently\nto isolate which part fails.\n\nFor the CTE rewrite pipeline and additional error patterns, see the `wren-sql` reference (run `wren skills get usage --full`).\n\n---\n\n## Workflow 3: Connecting a new data source\n\n1. Add a profile: `wren profile add --ui` (or `--interactive` / `--from-file`)\n2. Test connection: `wren profile debug`\n3. Test query: `wren --sql \"SELECT 1\"`\n4. Initialize project: `wren context init`\n5. Build manifest: `wren context build`\n6. Index: `wren memory index`\n7. Verify: `wren --sql \"SELECT * FROM <model> LIMIT 5\"`\n\n---\n\n## Workflow 4: After MDL changes\n\nWhen model YAML files are updated, rebuild and re-index:\n\n```bash\n# 1. Validate changes\nwren context validate\n\n# 2. Rebuild manifest\nwren context build\n\n# 3. Re-index schema memory\nwren memory index\n\n# 4. Verify\nwren --sql \"SELECT * FROM <changed_model> LIMIT 1\"\n```\n\n---\n\n## Command decision tree\n\n```text\nGet data back           → wren --sql \"...\"\nAggregation across dims → wren cube query --cube <name> --measures <m> (if cube defined)\nRanked / top-N          → wren cube query ... --order-by \"<measure>:desc\" --limit N\nSee translated SQL only → wren dry-plan --sql \"...\" (accepts -d <datasource> if no active profile)\nValidate against DB     → wren dry-run --sql \"...\"\nSchema context          → wren memory fetch -q \"...\"\nFilter by type/model    → wren memory fetch -q \"...\" --type T --model M --threshold 0\nStore confirmed query   → wren memory store --nl \"...\" --sql \"...\"\nFew-shot examples       → wren memory recall -q \"...\"\nIndex stats             → wren memory status\nRe-index after MDL change → wren memory index\nShow project context    → wren context show\nRebuild manifest        → wren context build\nCheck profile           → wren profile debug\nSwitch profile          → wren profile switch <name>\n```\n\n---\n\n## Cube Query Workflow\n\nWhen the user asks an aggregation question (e.g., \"total revenue by month\",\n\"top customers\"), check if the MDL defines cubes before writing raw SQL.\n\n### Step 1: Discover cubes\n\n```bash\nwren cube list\n```\n\nIf cubes exist and cover the user's question, prefer cube query over raw SQL.\nLower error rate, especially for small / local models — agents don't have to\nhand-write GROUP BY / DATE_TRUNC.\n\n### Step 2: Inspect cube structure\n\n```bash\nwren cube describe <cube_name>\n```\n\nShows the cube's baseObject, measures (with expressions), dimensions,\ntime dimensions, and hierarchies.\n\n### Step 3: Match user's question to cube measures + dimensions\n\n| User phrase | Maps to |\n|---|---|\n| \"total revenue\" | `--measures total` |\n| \"by month\" | `--time-dimension \"order_date:month\"` |\n| \"in 2024\" | `--time-dimension \"order_date:month:2024-01-01,2025-01-01\"` |\n| \"for completed orders\" | `--filter \"status:eq:completed\"` |\n| \"top N customers\" | `--dimensions customer --order-by \"total:desc\" --limit N` |\n| \"worst / lowest / bottom N\" | `--order-by \"<measure>:asc\" --limit N` |\n| \"sorted by name\" | `--order-by \"<dimension>:asc\"` |\n\n### Step 4: Execute via CLI flags OR JSON input\n\nCLI flags:\n\n```bash\nwren cube query \\\n  --cube revenue \\\n  --measures total,order_count \\\n  --time-dimension \"order_date:month:2024-01-01,2025-01-01\" \\\n  --filter \"status:eq:completed\" \\\n  --order-by \"total:desc\" \\\n  --limit 100\n```\n\n**`--limit` without `--order-by` is not a top-N.** It returns an arbitrary N rows\n(or the earliest N by the time dimension when one is present). Whenever the user\nasks for \"top\", \"best\", \"worst\", or \"largest\", add `--order-by`.\n\nOrdering rules: the member must be one this query already selects (a measure,\ndimension, or time dimension); direction is lowercase `asc` or `desc`; each member\nmay appear once. Omitting `--order-by` keeps the previous default ordering.\n\nName the member as the cube declares it. A time dimension appears in the generated\nSQL as `<name>__<granularity>`, but `--order-by` wants the declared name:\n`--time-dimension \"order_date:month\" --order-by \"order_date:desc\"`.\n\nJSON input (good for agent-generated structured queries):\n\n```bash\necho '{\"cube\":\"revenue\",\"measures\":[\"total\"]}' | wren cube query --from -\n```\n\nAdd `--sql-only` to print the generated SQL without executing — useful for\nverification before paying for execution on a remote warehouse.\n\n### Step 5: Error recovery\n\n| Error | Action |\n|---|---|\n| `Unknown measure 'X'` | `wren cube describe <cube>` for available measures |\n| `Unknown dimension 'X'` | `wren cube describe <cube>` for available dimensions |\n| `Cube 'X' not found` | `wren cube list` |\n| `Cannot order by member 'X': member is not selected by the query` | Add `X` to `--measures` / `--dimensions`, or order by a member already selected |\n| `Cannot order by member 'X' more than once` | Drop the duplicate — each member may appear once in `--order-by` |\n| `` unknown variant `DESC`, expected `asc` or `desc` `` | Directions are lowercase: use `desc`, not `DESC` |\n| `Circular dependency detected` | Derived measure references itself — inspect the cube YAML |\n\n### When NOT to use cube query\n\nFall back to `wren --sql` when:\n\n- Custom JOINs across multiple models\n- Window functions, CTEs, or subqueries\n- Queries with no aggregation\n- No cubes defined in the MDL\n\n---\n\n## Aggregation decision tree\n\n```text\nUser question → Is it an aggregation question?\n                (SUM, COUNT, AVG, GROUP BY, \"by month\", \"per customer\", ...)\n  ├── Yes → Are cubes defined? (`wren cube list` once at start of session)\n  │         ├── Yes → Does a cube cover the question? (`wren cube describe`)\n  │         │         ├── Yes → Use `wren cube query` (preferred — lower error rate)\n  │         │         └── No  → Write raw SQL with `wren --sql`\n  │         └── No  → Write raw SQL with `wren --sql`\n  └── No  → Write raw SQL with `wren --sql` (look for memory recall first)\n```\n\n---\n\n## Things to avoid\n\n- Do not guess model or column names — check context first\n- Do not store failed queries or queries the user said are wrong\n- Do not skip storing successful queries with a clear NL question — default is to store\n- Do not re-index before every query — once per MDL change\n- Do not pass passwords via `--connection-info` if shell history is shared — use profiles (`wren profile add`) or `--connection-file`\n","evals/spodbtify_ab/README.md":"# Spodbtify A/B Eval\n\nThis eval compares two workflows for turning the same DuckDB database into a\nqueryable Wren semantic layer, then scoring how well an AI coding agent answers\n20 analytical Spotify playlist questions.\n\nThe eval is intentionally agent-agnostic. It is not tied to Claude Code: use\nthe same spec with Claude, Codex, Cursor, an MCP client, or any runner that can\nconsume a prompt file and write an answer file.\n\n## Dataset\n\nExpected local dataset:\n\n```bash\nexport SPODBTIFY_DUCKDB_PATH=/path/to/spodbtify.duckdb\n```\n\nExpected dbt project artifacts:\n\n```bash\nexport SPODBTIFY_DBT_PROJECT_DIR=/path/to/spodbtify-dbt-project\n\n$SPODBTIFY_DBT_PROJECT_DIR/target/manifest.json\n$SPODBTIFY_DBT_PROJECT_DIR/target/catalog.json\n$SPODBTIFY_DBT_PROJECT_DIR/target/run_results.json\n$SPODBTIFY_DBT_PROJECT_DIR/target/compiled/\n```\n\nThe dataset is intentionally not checked into this repository.\n\n## Files\n\n- `spodbtify_ab_eval.json` is the canonical eval spec: workflows, controls,\n  table inventory, scoring rubric, questions, and result schema.\n- `agent_output.schema.json` is an optional JSON Schema for agents that support\n  structured output.\n- `run_eval.py` validates the spec, generates prompts, runs arbitrary agent\n  command templates, and summarizes score files.\n\n## Quick Validation\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py validate\n```\n\n## Generate Prompts\n\nPrint one prompt:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py prompt \\\n  --agent codex \\\n  --workflow dbt_integrated \\\n  --question 1\n```\n\nMaterialize prompt files for all questions without running an agent:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py run-agent \\\n  --agent codex \\\n  --workflow both \\\n  --dry-run\n```\n\n## Run With Any Agent CLI\n\n`run-agent` accepts a shell command template. These placeholders are available:\n\n- `{prompt_file}`\n- `{output_file}`\n- `{agent}`\n- `{workflow}`\n- `{question_id}`\n- `{schema_file}`\n\nExample shape:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py run-agent \\\n  --agent codex \\\n  --workflow schema_only \\\n  --command 'codex exec --sandbox danger-full-access --output-schema {schema_file} -o {output_file} - < {prompt_file}'\n```\n\nUse the equivalent command for Claude or another agent:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py run-agent \\\n  --agent claude \\\n  --workflow dbt_integrated \\\n  --command 'claude -p \"$(cat {prompt_file})\" > {output_file}'\n```\n\nExact CLI flags vary by tool and installation. The eval contract is the prompt\nfile in, answer file out. Keep one fresh session per workflow and do not share\nanswers or memory between workflows.\n\n## Scoring Contract\n\nAgents should return JSON with:\n\n```json\n{\n  \"question_id\": 1,\n  \"workflow\": \"dbt_integrated\",\n  \"agent\": \"codex\",\n  \"selected_tables\": [\"Top_Artists\", \"Artist_Collaborators\"],\n  \"sql\": \"SELECT ...\",\n  \"answer\": \"The analytical answer...\",\n  \"notes\": \"Optional caveats\"\n}\n```\n\nAfter a human or external grader assigns the three 0/1/2 scores for each\nquestion, store them in the score schema shown by:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py new-score-template \\\n  --agent codex \\\n  --workflow dbt_integrated \\\n  --output /tmp/codex-dbt-score.json\n```\n\nThen summarize:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py score --scores /tmp/codex-dbt-score.json\n```\n\nDo not commit agent answer files, run directories, or scored result files.\n","skills/.claude-plugin/marketplace.json":"{\n  \"name\": \"wren\",\n  \"owner\": {\n    \"name\": \"Canner\",\n    \"email\": \"dev@cannerdata.com\"\n  },\n  \"metadata\": {\n    \"description\": \"Wren Engine — discovery stub for AI agents. Actual workflow guides and prompt helpers ship inside the wren CLI (pip install wrenai) and are served by `wren skills get` / `wren ask`.\"\n  },\n  \"plugins\": [\n    {\n      \"name\": \"wren\",\n      \"source\": \".\",\n      \"description\": \"Wren CLI for AI agents — semantic SQL over 22+ databases. Installs a single discovery stub that points the agent at the wren CLI for all workflow content.\",\n      \"version\": \"1.0.0\",\n      \"keywords\": [\n        \"wren\",\n        \"sql\",\n        \"mdl\",\n        \"cli\",\n        \"semantic-layer\",\n        \"postgres\",\n        \"bigquery\",\n        \"snowflake\",\n        \"mysql\",\n        \"clickhouse\",\n        \"trino\",\n        \"mssql\",\n        \"databricks\",\n        \"redshift\",\n        \"spark\",\n        \"athena\",\n        \"oracle\",\n        \"duckdb\"\n      ]\n    }\n  ]\n}\n","skills/.claude-plugin/plugin.json":"{\n  \"name\": \"wren\",\n  \"description\": \"AI agent skills for Wren Engine CLI — semantic SQL layer for 22+ data sources.\",\n  \"version\": \"1.0.0\",\n  \"author\": {\n    \"name\": \"Canner\",\n    \"url\": \"https://www.getwren.ai/\"\n  },\n  \"homepage\": \"https://www.getwren.ai/\",\n  \"repository\": \"https://github.com/Canner/WrenAI\",\n  \"license\": \"Apache-2.0\",\n  \"keywords\": [\n    \"wren\",\n    \"sql\",\n    \"mdl\",\n    \"cli\",\n    \"semantic-layer\",\n    \"data-source\",\n    \"postgres\",\n    \"bigquery\",\n    \"snowflake\",\n    \"mysql\",\n    \"clickhouse\",\n    \"trino\",\n    \"mssql\",\n    \"databricks\",\n    \"redshift\",\n    \"spark\",\n    \"athena\",\n    \"oracle\",\n    \"duckdb\"\n  ]\n}\n","skills/AUTHORING.md":"# Skill Authoring Guide\n\n> **Where to author new skill content:**\n> **NOT here under `skills/`.** New skill guides ship as Python package data\n> in [`core/wren/src/wren/skills_content/<name>/`](../core/wren/src/wren/skills_content/),\n> served at runtime by `wren skills get <name>`. That keeps the content\n> version-aligned with the installed `wren` CLI — no skill cache, no\n> `versions.json` drift hack.\n>\n> This `skills/` tree only contains the **distribution stubs** that AI clients\n> install (one new discovery stub `wren/`, plus five deprecated redirect\n> stubs kept for one release).\n\nThe skill content format below still follows the\n[Agent Skills](https://agentskills.io/) open spec — the only thing that\nchanged is where the files live and how they reach the agent.\n\n---\n\n## Directory layout (under `core/wren/src/wren/skills_content/`)\n\n```text\n<name>/\n├── SKILL.md              # Required — frontmatter + workflow instructions\n├── references/           # Optional — reference docs, served by `wren skills get <name> --full`\n│   ├── some-topic.md\n│   └── another-topic.md\n└── scripts/              # Optional — bundled scripts, served by `wren skills get <name> --script <s>`\n```\n\n---\n\n## Frontmatter\n\n```yaml\n---\nname: <name>              # matches the parent directory; lowercase, hyphens\ndescription: \"What this skill does and when to trigger it. Include trigger\n  keywords so an AI client can match it to user intent.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n```\n\n**Drop the `version:` field.** Content version = the installed wrenai\nversion, since the SKILL.md ships inside the wheel.\n\n---\n\n## Progressive disclosure\n\n| Tier | Content | Loaded when |\n|------|---------|-------------|\n| 1 — Discovery stub | `skills/wren/SKILL.md` frontmatter description (~few hundred tokens) | At every AI-client session start |\n| 2 — Main guide | `wren skills get <name>` | The agent runs the command when the user's task matches |\n| 3 — References | `wren skills get <name> --full` | The agent opts in when it needs depth |\n| 3 — Scripts | `wren skills get <name> --script <s>` | Same |\n\nKeep `SKILL.md` under ~500 lines. Move reference-only content into `references/`.\n\n---\n\n## What goes where\n\n### Keep in `SKILL.md`\n- Step-by-step workflow the agent follows\n- Decision criteria and branching logic\n- Short commands the agent needs immediately\n- Quick reference tables (file paths, phase mappings, etc.)\n\n### Move to `references/`\n- Output templates (report formats, plan file formats)\n- Per-case investigation details\n- Large lookup tables\n- Anything that would push `SKILL.md` past ~500 lines\n\nCross-link inside the same skill by reference name (delivered via `--full`):\n```markdown\nFor the CTE rewrite pipeline, see the `wren-sql` reference (run\n`wren skills get usage --full`).\n```\n\nCross-link to another skill via its CLI command:\n```markdown\nFor day-to-day querying after setup, run `wren skills get usage`.\n```\n\nCross-link to a general doc:\n```markdown\nFor troubleshooting, see [`connect.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md).\n```\n\n---\n\n## Naming\n\n| Item | Convention | Example |\n|------|------------|---------|\n| Skill directory | `kebab-case` | `generate-mdl/` |\n| `name` field | same as directory | `generate-mdl` |\n| Reference files | descriptive `kebab-case` | `memory.md`, `wren-sql.md` |\n| Script files | descriptive `kebab-case` | `introspect_dlt.py` |\n\n---\n\n## Registering a new skill\n\n1. Create the directory under `core/wren/src/wren/skills_content/<name>/` with `SKILL.md` (and optional `references/`, `scripts/`).\n2. Author the content following the format above.\n3. Add a test to `core/wren/tests/unit/test_skills_cli.py` verifying `wren skills get <name>` returns the guide and `--full` inlines its references (if any).\n4. Done — there is no separate `versions.json` / `index.json` to update; the package-data ships with the wheel and `wren skills list` enumerates it automatically.\n\nNo `bash skills/check-versions.sh` step — version drift is impossible by\nconstruction (content travels with the binary).\n","skills/README.md":"# Wren Engine — Agent Skill Distribution\n\nThe actual skill content (workflow guides, reference docs, prompt helpers)\n**lives inside the `wren` CLI**. This directory ships a single discovery stub\nthat an AI client installs once; the stub then tells the agent to fetch\neverything else from the CLI at runtime (so content always matches the\ninstalled wrenai version).\n\nSee [`SKILLS.md`](SKILLS.md) for the full design and command surface.\n\n## Install\n\n### The CLI itself (where all skill content lives)\n\n```bash\npip install wrenai\n```\n\n### The discovery stub (so an AI client knows the CLI exists)\n\n#### Option 1 — Claude Code plugin\n\n```text\n/plugin marketplace add Canner/WrenAI --path skills\n/plugin install wren@wren\n```\n\n#### Option 2 — `npx skills`\n\n```bash\nnpx skills add Canner/WrenAI\n```\n\nThe installer auto-detects your AI client. To target a specific one, add\n`--agent <name>` (e.g. `claude-code`, `cursor`, `windsurf`, `cline`).\n\n#### Option 3 — local install script\n\n```bash\nbash skills/install.sh                 # install the discovery stub\nbash skills/install.sh --force         # overwrite existing\n```\n\n## What the agent does with the stub\n\nOnce installed, the agent reads `wren/SKILL.md` and learns to call:\n\n```bash\nwren skills list                        # discover workflow guides\nwren skills get onboarding              # fetch a guide (one of 5 names)\nwren docs connection-info <ds>          # connection fields for a data source\nwren ask \"<question>\" --guided|--direct # wrap a prompt for an agent\n```\n\n## Requirements\n\n- `wren` CLI installed (`pip install wrenai` or `pip install \"wrenai[<extras>]\"`)\n- A database connection (configured via `wren profile add`)\n- An AI client that supports skills (Claude Code, Cursor, Cline, etc.)\n","skills/SKILLS.md":"# Wren Engine — Agent Skills\n\nThe actual workflow guides, reference docs, and prompt helpers live **inside\nthe `wren` CLI itself**, so they always match the installed wrenai\nversion (no skill cache, no version drift).\n\nThis directory ships a single discovery stub ([`wren/SKILL.md`](wren/SKILL.md))\nthat an AI client can install. Once the agent reads the stub, it learns to\nfetch everything else from the CLI on demand:\n\n```bash\nwren skills list                        # all available workflow guides\nwren skills get <name>                  # fetch a guide\nwren skills get <name> --full           # include the guide's reference docs\nwren skills get <name> --script <s>     # fetch a bundled script\n\nwren docs connection-info <ds>          # connection fields for a data source\n\nwren ask \"<question>\" --guided          # wrap a question for a weaker LLM\nwren ask \"<question>\" --direct          # wrap a question for a stronger LLM\n```\n\n## Install\n\n```bash\npip install wrenai                 # the CLI (everything is here)\nnpx skills add Canner/WrenAI            # install the discovery stub for AI clients\n```\n\nOr via Claude Code's plugin marketplace:\n\n```text\n/plugin marketplace add Canner/WrenAI --path skills\n/plugin install wren@wren\n```\n\n## Writing a new skill\n\nNew skill guides ship as Python package data in\n[`core/wren/src/wren/skills_content/<name>/`](../core/wren/src/wren/skills_content/),\nnot as a new directory under this `skills/` tree. See\n[`AUTHORING.md`](AUTHORING.md).\n","skills/index.json":"{\n  \"name\": \"wrenai\",\n  \"description\": \"Discovery stub for the Wren AI agent skill bundle. The actual workflow guides and prompt helpers live inside the wren CLI (`pip install wrenai`) and are served by `wren skills get` / `wren ask`.\",\n  \"homepage\": \"https://wren.ai\",\n  \"repository\": \"https://github.com/Canner/WrenAI\",\n  \"license\": \"Apache-2.0\",\n  \"skills\": [\n    {\n      \"name\": \"wren\",\n      \"description\": \"Wren CLI discovery stub. The actual workflow guides live inside the wren CLI; this stub teaches an AI agent to call `wren skills list`, `wren skills get <name>`, and `wren ask <prompt> --guided|--direct`. Use whenever the user asks a data question, wants to install / set up Wren Engine, connect a new database, load SaaS data via dlt, generate or regenerate an MDL project, enrich a project with business context, or build & deploy a shareable GenBI web app / dashboard from a project's semantic layer (Vercel / Cloudflare).\",\n      \"tags\": [\n        \"wren\",\n        \"discovery-stub\",\n        \"cli\",\n        \"sql\",\n        \"mdl\",\n        \"semantic-layer\",\n        \"genbi\"\n      ],\n      \"repository\": \"https://github.com/Canner/WrenAI/tree/main/skills/wren\"\n    }\n  ]\n}\n","skills/wren/SKILL.md":"---\nname: wren\ndescription: \"Wren CLI for AI agents — a semantic SQL layer over 22+ databases (Postgres, MySQL, BigQuery, Snowflake, Spark, …). The actual workflow guides live inside the `wren` CLI itself; this is just a discovery stub. Use whenever the user asks a data question (how many, show me, top N, compare, trend, breakdown, metric, revenue, customers, orders), wants to install / set up Wren Engine, connect a new database, connect SaaS data via dlt (HubSpot, Stripe, Salesforce, GitHub, Slack), generate or regenerate an MDL project from a database schema, enrich a project with business context (enum meanings, units, cubes like ARR / DAU / churn), or turn a project's context layer into a shareable GenBI web app / dashboard and deploy it to Vercel or Cloudflare. Triggers: 'install wren', 'set up wren engine', 'connect database to wren', 'connect SaaS to wren', 'load hubspot / stripe / salesforce data', 'generate mdl', 'scaffold wren project', 'enrich wren context', 'augment my project', 'add cubes', 'build a dashboard', 'make a shareable analytics app', 'deploy my context layer as a web app', 'genbi app', 'wren onboarding', 'wren usage', 'wren generate mdl', 'wren dlt connector', 'wren enrich context', 'wren genbi'.\"\nlicense: Apache-2.0\nallowed-tools: Bash(wren:*)\n---\n\n# Wren CLI\n\nThis is a discovery stub. The actual workflow guides and prompt helpers\nlive inside the `wren` CLI itself, so they always match the installed\nwrenai version (no skill cache, no version drift).\n\nInstall: `pip install wrenai`.\n\n## Workflow guides\n\n```bash\nwren skills list                        # all available workflow guides\nwren skills get onboarding              # set up Wren end-to-end\nwren skills get usage                   # day-to-day querying\nwren skills get generate-mdl            # generate MDL from a database schema\nwren skills get dlt-connector           # connect SaaS sources via dlt\nwren skills get enrich-context          # add business context (units, enums, cubes)\nwren skills get genbi                   # build & deploy a shareable GenBI web app\n# add --full to include the skill's reference docs\n# add --script <name> to fetch a bundled script (e.g. dlt-connector / introspect_dlt)\n```\n\n## Reference docs\n\nFull reference docs live on the web: <https://github.com/Canner/WrenAI/tree/main/docs/core>\n\n```bash\nwren docs connection-info <ds>          # required + optional connection fields for a data source\n```\n\n## Prompt enhancement (wraps a user question for an agent)\n\n```bash\nwren ask \"<question>\" --guided          # for weaker LLMs (strict task flow)\nwren ask \"<question>\" --direct          # for stronger LLMs (minimal wrapping)\n```\n\n## Day-to-day data commands (not a sub-app — top-level)\n\n```bash\nwren --sql '...'                        # execute SQL through the MDL layer\nwren query --sql '...'                  # same, explicit\nwren dry-plan --sql '...'               # transpile only, no DB hit\nwren context show / build / validate    # project / MDL lifecycle\nwren profile add / list / switch        # named connection profiles\nwren memory index / recall / store      # semantic memory (needs `[memory]` extra)\n```\n\nRun `wren --help` for the full surface; load the matching `wren skills get\n<name>` guide before driving any multi-step workflow.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nRepository-wide instructions live in `.claude/CLAUDE.md`. Please refer to that file for shared build commands, architecture, and conventions.\n\n**Before opening a pull request, read the \"Contribution Bar\" section of that file.** It sets out what is expected of a change here — reproduce a failure before claiming one, label honestly, make tests exercise the code they cover — and what will be closed rather than reviewed.\n\nPer-module instructions live in `core/<module>/.claude/CLAUDE.md`, each with its own `AGENTS.md` pointing at it.\n","isInternal":false,"tokens":124,"sizeBytes":552},{"name":"SKILLS.md","path":"skills/SKILLS.md","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/skills/SKILLS.md","title":"skills - Workspace Skills & Capabilities Index","category":"root-instruction","format":"markdown","content":"# Wren Engine — Agent Skills\n\nThe actual workflow guides, reference docs, and prompt helpers live **inside\nthe `wren` CLI itself**, so they always match the installed wrenai\nversion (no skill cache, no version drift).\n\nThis directory ships a single discovery stub ([`wren/SKILL.md`](wren/SKILL.md))\nthat an AI client can install. Once the agent reads the stub, it learns to\nfetch everything else from the CLI on demand:\n\n```bash\nwren skills list                        # all available workflow guides\nwren skills get <name>                  # fetch a guide\nwren skills get <name> --full           # include the guide's reference docs\nwren skills get <name> --script <s>     # fetch a bundled script\n\nwren docs connection-info <ds>          # connection fields for a data source\n\nwren ask \"<question>\" --guided          # wrap a question for a weaker LLM\nwren ask \"<question>\" --direct          # wrap a question for a stronger LLM\n```\n\n## Install\n\n```bash\npip install wrenai                 # the CLI (everything is here)\nnpx skills add Canner/WrenAI            # install the discovery stub for AI clients\n```\n\nOr via Claude Code's plugin marketplace:\n\n```text\n/plugin marketplace add Canner/WrenAI --path skills\n/plugin install wren@wren\n```\n\n## Writing a new skill\n\nNew skill guides ship as Python package data in\n[`core/wren/src/wren/skills_content/<name>/`](../core/wren/src/wren/skills_content/),\nnot as a new directory under this `skills/` tree. See\n[`AUTHORING.md`](AUTHORING.md).\n","isInternal":false,"tokens":364,"sizeBytes":1490},{"name":"CLAUDE.md","path":".claude/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/.claude/CLAUDE.md","title":".claude - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nWren is an open-source semantic engine for MCP clients and AI agents. It translates SQL queries through a semantic layer (MDL — Modeling Definition Language) and executes them against 22+ data sources (PostgreSQL, BigQuery, Snowflake, Spark, etc.). The Rust engine is powered by Apache DataFusion (upstream, crates.io v55).\n\nThe previous WrenAI services (`wren-ui/`, `wren-ai-service/`, `wren-launcher/`, `docker/`, `deployment/`) were moved to the `legacy/v1` branch (tag `v1-final`) as of the wren-engine import. Active development is focused on the Open Context Engine.\n\n## Repository Structure\n\n```\ncore/\n├── wren-core/        Rust semantic engine (Cargo workspace; crates.io: wren-semantic-core, lib name wren_core)\n├── wren-core-base/   Shared Rust crate — manifest types (Model, Column, Metric, Relationship, View) + ManifestBuilder (crates.io: wren-core-base)\n├── wren-core-py/     PyO3 bindings exposing wren-core to Python (PyPI: wren-core)\n├── wren-core-wasm/   WebAssembly build of wren-core for in-browser semantic SQL (npm: wren-core-wasm)\n├── wren/             Python SDK and CLI — `wren` command, profile/context/memory management (PyPI: wrenai)\n└── wren-mdl/         MDL JSON schema definition\n\ndocs/core/            Module documentation\nexamples/             Example projects (placeholder — to be populated)\nskills/               CLI-based agent skills (wren-generate-mdl, wren-usage, wren-dlt-connector, wren-onboarding)\nscripts/              Repo helper scripts\n```\n\n## Build & Development Commands\n\n### core/wren-core (Rust)\n```bash\ncd core/wren-core\ncargo check --all-targets                                  # compile check\ncargo test --lib --tests --bins                            # tests (set RUST_MIN_STACK=8388608)\ncargo fmt --all                                            # format\ncargo clippy --all-targets --all-features -- -D warnings   # lint\ntaplo fmt                                                  # format Cargo.toml files\n```\n\nMost unit tests live in `core/wren-core/core/src/mdl/mod.rs`. SQL end-to-end tests use sqllogictest files in `core/wren-core/sqllogictest/test_files/`.\n\n### core/wren-core-py (Python bindings)\n```bash\ncd core/wren-core-py\njust install      # uv sync (deps only; --no-install-project)\njust develop      # build dev wheel with maturin\njust test-rs      # Rust tests (cargo test --no-default-features)\njust test-py      # Python tests (pytest)\njust test         # both\njust format       # cargo fmt + ruff + taplo\n```\n\n### core/wren-core-wasm (WASM)\n```bash\ncd core/wren-core-wasm\njust build        # wasm-pack build (browser target)\njust test         # wasm-pack test\n```\nOutputs a ~68 MB WASM binary; distributed via npm and unpkg (jsDelivr's 50 MB per-file CDN limit blocks it).\n\n### core/wren (SDK & CLI)\n```bash\ncd core/wren\njust install              # uv sync (locked prebuilt wren-core-py wheel from PyPI; no Rust build)\njust install-all          # with all optional extras (incl. memory)\njust install-extra <e>    # e.g. just install-extra postgres\njust install-memory       # memory extra (lancedb + sentence-transformers)\njust install-local        # engine dev: uv sync + build local wheel + overlay\njust use-local-core       # rebuild + re-overlay after Rust changes\njust dev                  # run `wren` CLI\njust test                 # pytest tests/\njust test-memory          # memory-specific tests\njust lint                 # ruff format --check + ruff check\njust format               # ruff auto-fix\njust build                # uv build (produces wheel)\n```\n\nUses `uv` (not Poetry). `pyproject.toml` uses `hatchling` as build backend. Optional extras: `postgres`, `mysql`, `bigquery`, `snowflake`, `clickhouse`, `trino`, `mssql`, `databricks`, `redshift`, `spark`, `athena`, `oracle`, `memory`, `all`, `dev`.\n\n## Architecture: Query Flow\n\n```\nSQL query\n  → wren CLI / wren-core-py\n  → wren-core (Rust): MDL analysis → logical plan → optimization\n  → DataFusion (query planning, upstream crates.io v55)\n  → connector-specific SQL (Ibis / sqlglot)\n  → native execution on the target data source\n```\n\n## Key Architecture Details\n\n**wren-core internals** (`core/wren-core/core/src/`):\n- `mdl/` — Core MDL processing: `WrenMDL` (manifest + symbol table), `AnalyzedWrenMDL` (with lineage), function definitions per dialect (scalar/aggregate/window), type planning\n- `logical_plan/analyze/` — DataFusion analyzer rules: `ModelAnalyzeRule` (TableScan → ModelPlanNode), scope tracking, access control (RLAC/CLAC), view expansion, relationship chain resolution\n- `logical_plan/optimize/` — Optimization passes: type coercion, timestamp simplification\n- `sql/` — SQL parsing and analysis\n\n**Manifest types** (`core/wren-core-base/src/mdl/`):\n- `manifest.rs` — `Manifest`, `Model`, `Column`, `Metric`, `Relationship`, `View`, `RowLevelAccessControl`, `ColumnLevelAccessControl`\n- `builder.rs` — Fluent `ManifestBuilder` API\n- Uses `wren-manifest-macro` for auto-generating Pydantic-compatible Python classes\n\n## Known wren-core Limitations\n\n**ModelAnalyzeRule — correlated subquery column resolution**: cannot resolve outer column references inside correlated subqueries; only sees the subquery's own table scope. Affects TPCH Q2, Q4, Q15, Q17, Q20, Q21, Q22.\n\n## Conventions\n\n- **Commits**: Conventional commits (`feat:`, `fix:`, `chore:`, `refactor:`, `test:`, `docs:`, `perf:`, `deps:`). Releases are automated via release-please with independent release lines per module.\n- **Rust**: format with `cargo fmt`, lint with `clippy -D warnings`, TOML with `taplo`.\n- **Python**: format and lint with `ruff` (line-length 88, target Python 3.11). Both `core/wren-core-py` and `core/wren` use uv.\n- **DataFusion**: upstream `datafusion` v55 from crates.io (no longer the Canner fork).\n- **Snapshot testing**: wren-core uses `insta` for Rust snapshot tests.\n- **CI**: Per-module path-filtered workflows trigger only on changes inside that module.\n\n## Contribution Bar\n\nRead this before opening a pull request. These rules exist because a large share of\nincoming PRs fail them; a PR that violates one will be closed rather than iterated on.\n\n### Prove the problem before fixing it\n\n- **Do not assert the behaviour of an external system you have not observed.** If you\n  claim an engine, driver, or library rejects some input, reproduce it and paste the\n  actual error into the description. Unevidenced claims — \"the parser rejects a trailing\n  semicolon\", \"some clients reject this\" — are rejected on sight; several such claims\n  have turned out to be false when tested.\n- **Do not add a guard for a state you have not shown to be reachable.** Trace the call\n  path and say in the description how the bad value gets there. Where the repo already\n  validates (see \"Validation boundaries\" in `core/wren/.claude/CLAUDE.md`), re-validating\n  downstream is dead code.\n- If you cannot reproduce a failure, you have a hypothesis, not a bug. **Open an issue\n  with the reproduction attempt, not a PR.**\n\n### Label honestly\n\n- `fix:` requires a reproducible failure that the change repairs. A change with no\n  observable difference in behaviour is `refactor:` or `chore:`.\n- The description must say what changes for a user. \"Improves robustness\" and\n  \"hardens X\" are not answers.\n\n### Tests must exercise the code you changed\n\n- A test that reads a source file as text and asserts on substrings tests nothing. Call\n  the function; assert on what it returns, or on what it passes downstream.\n- Every assertion must be capable of failing. An assertion about a condition the code\n  cannot produce is noise.\n- Confirm the test actually runs in CI. Some test files are gated behind optional extras\n  or containers and are skipped by the default jobs — a service-free test placed in one\n  of those files never executes.\n\n### One change, once\n\n- Search open PRs before opening a new one. Duplicate PRs against the same file are\n  closed without review.\n- A mechanical change repeated across several files or connectors belongs in **one** PR,\n  not one PR per file. Reviewers need to see the resulting convention in a single diff.\n\n### Respect decisions already recorded\n\n- If a comment or a test documents a deliberate choice, do not silently reverse it.\n  Quote it in the description and argue why it should change.\n\n### Keep the branch current\n\n- Rebase onto the target branch before requesting review. A branch whose last CI run was\n  against an old base can merge cleanly and still be broken — a textually clean merge is\n  not a semantically correct one.\n","isInternal":false,"tokens":2141,"sizeBytes":8727},{"name":"SKILL.md","path":"core/wren/src/wren/skills_content/dlt-connector/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/core/wren/src/wren/skills_content/dlt-connector/SKILL.md","title":"dlt-connector","category":"anthropic-skill","format":"markdown","content":"---\nname: dlt-connector\ndescription: \"Connect SaaS data (HubSpot, Stripe, Salesforce, GitHub, Slack, etc.) to Wren Engine for SQL analysis. Guides the user through the full flow: install dlt, pick a SaaS source, set up credentials, run the data pipeline into DuckDB, then auto-generate a Wren semantic project from the loaded data. Use this skill whenever the user mentions: connecting SaaS data, importing data from an API, dlt pipelines, loading HubSpot/Stripe/Salesforce/GitHub/Slack data, querying SaaS data with SQL, or setting up a new data source from a REST API. Also trigger when the user already has a dlt-produced DuckDB file and wants to create a Wren project from it.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# wren-dlt-connector\n\n> Reference docs (`dlt_sources`) and the `introspect_dlt` script are bundled. Pull references with `wren skills get dlt-connector --full`; fetch a script with `wren skills get dlt-connector --script <name>`.\n\nConnect SaaS data to Wren Engine for SQL analysis — from zero to a verified, queryable project in one conversation.\n\n## Who this is for\n\nData analysts who know SQL and some Python, but may not have used dlt or Wren before. Explain concepts briefly when they first appear, but don't over-explain things a SQL-literate person would already know.\n\n## Overview\n\nThis skill walks through a four-phase workflow:\n\n1. **Extract** — Use dlt (data load tool) to pull data from a SaaS API into a local DuckDB file\n2. **Model** — Introspect the DuckDB schema and auto-generate a Wren semantic project (YAML models, relationships, profile)\n3. **Build & Verify** — Build the project and run actual SQL queries to confirm everything works end-to-end\n4. **Handoff** — Show the user their data and next steps\n\nThe user might enter at any phase. Ask which phase they're starting from — they may already have a `.duckdb` file and just need phases 2–4.\n\n**The goal is a project that actually queries successfully, not just files that look correct.** Always run the verification step before declaring success.\n\n## Critical: DuckDB catalog naming\n\nWhen wren engine connects to a DuckDB file, it ATTACHes it using the filename (without `.duckdb` extension) as the catalog alias:\n\n```\nATTACH DATABASE 'stripe_data.duckdb' AS \"stripe_data\" (READ_ONLY)\n```\n\nThis means **every model's `table_reference.catalog` must equal the DuckDB filename stem**. If the file is `hubspot.duckdb`, the catalog is `hubspot`. If it's `my_pipeline.duckdb`, the catalog is `my_pipeline`.\n\nGetting this wrong causes \"table not found\" errors at query time. The `introspect_dlt.py` script handles this automatically.\n\n## Critical: Type normalization\n\nColumn types must be normalized using wren SDK's `type_mapping.parse_type()` function, which uses sqlglot to convert database-specific types (like DuckDB's `HUGEINT`, `TIMESTAMP WITH TIME ZONE`) into canonical SQL types that wren-core understands. Do not hardcode type mappings — always delegate to `parse_type(raw_type, \"duckdb\")`.\n\nThe `introspect_dlt.py` script does this automatically when wren SDK is installed.\n\n## Phase 1: Extract — dlt Pipeline Setup\n\n### Step 1: Pick the SaaS source\n\nAsk the user which SaaS service they want to connect. Read `dlt_sources` for a list of popular verified sources and their auth requirements. If the source isn't listed, check whether dlt has a verified source for it by searching `dlthub.com/docs/dlt-ecosystem/verified-sources`.\n\n### Step 2: Install dlt\n\n```bash\npip install \"dlt[duckdb]\" --break-system-packages\n```\n\n### Step 3: Write the pipeline script\n\nCreate a Python script that:\n1. Imports the dlt source function for the chosen SaaS\n2. Configures the pipeline with `destination='duckdb'` and a local file path\n3. Runs the pipeline with `pipeline.run(source)`\n\nHere's the general pattern — adapt it per source (check `dlt_sources` for source-specific templates):\n\n```python\nimport dlt\n\npipeline = dlt.pipeline(\n    pipeline_name=\"<source>_pipeline\",\n    destination=\"duckdb\",\n    dataset_name=\"<source>_data\",\n)\n\n# Source-specific: check the dlt_sources reference for auth patterns\nsource = <source_function>(api_key=dlt.secrets.value)\n\ninfo = pipeline.run(source)\nprint(info)\n```\n\n### Step 4: Set up credentials\n\ndlt reads credentials from environment variables or `.dlt/secrets.toml`. The simplest approach for a one-time run:\n\n```bash\n# Set the credential as an environment variable\n# The exact variable name depends on the source — check the dlt_sources reference\nexport SOURCES__<SOURCE>__API_KEY=\"the-actual-key\"\n```\n\nAsk the user for their API key or token. Remind them:\n- Never commit credentials to git\n- Environment variables are the simplest way for a one-time run\n- For repeated use, they can create `.dlt/secrets.toml`\n\n### Step 5: Run the pipeline\n\n```bash\npython <pipeline_script>.py\n```\n\nAfter the run, confirm:\n1. The pipeline completed without errors\n2. A `.duckdb` file was created (usually at `<pipeline_name>.duckdb`)\n3. Print discovered tables and their column counts\n\n```python\nimport duckdb\n\ncon = duckdb.connect(\"<pipeline_name>.duckdb\", read_only=True)\nfor row in con.execute(\"\"\"\n    SELECT table_schema, table_name,\n           (SELECT COUNT(*) FROM information_schema.columns c\n            WHERE c.table_schema = t.table_schema AND c.table_name = t.table_name) as col_count\n    FROM information_schema.tables t\n    WHERE table_schema NOT IN ('information_schema', 'pg_catalog')\n      AND table_name NOT LIKE '_dlt_%'\n    ORDER BY table_schema, table_name\n\"\"\").fetchall():\n    print(f\"  {row[0]}.{row[1]} ({row[2]} columns)\")\ncon.close()\n```\n\n## Phase 2: Model — Generate Wren Project\n\nRun the introspection script to auto-generate a complete Wren project from the DuckDB file:\n\n```bash\n# first fetch the script: wren skills get dlt-connector --script introspect_dlt > introspect_dlt.py\npython introspect_dlt.py \\\n    --duckdb-path <path-to-duckdb-file> \\\n    --output-dir <project-directory> \\\n    --project-name <name>\n```\n\nThis script:\n- Connects to the DuckDB file (read-only)\n- **Sets `table_reference.catalog` to the DuckDB filename stem** (matching wren engine's ATTACH behavior)\n- Discovers all tables and columns via `information_schema`\n- Filters out dlt internal tables (`_dlt_loads`, `_dlt_pipeline_state`, etc.)\n- Filters out dlt metadata columns (`_dlt_id`, `_dlt_load_id`, `_dlt_list_idx`) from model definitions\n- Detects parent-child relationships from `_dlt_parent_id` columns and table naming conventions\n- **Normalizes column types using `wren.type_mapping.parse_type()`** (sqlglot-based)\n- Generates a complete v5 YAML project (wren_project.yml, models/, relationships.yml, knowledge/rules/)\n\nAfter running, show the user what was generated:\n\n```bash\n# Show project summary\ncat <project-directory>/wren_project.yml\necho \"---\"\nls <project-directory>/models/\necho \"---\"\ncat <project-directory>/relationships.yml\n```\n\n### Verify model correctness\n\nSpot-check one generated model to confirm:\n1. `table_reference.catalog` matches the DuckDB filename (e.g., `stripe_data` for `stripe_data.duckdb`)\n2. `table_reference.schema` matches the DuckDB schema (usually `main`)\n3. No `_dlt_*` columns appear in the columns list\n4. Column types look reasonable (VARCHAR, BIGINT, BOOLEAN, TIMESTAMP, etc.)\n\n### Set up the connection profile\n\nCreate a Wren profile so the user can query without specifying connection details every time. The `url` must point to the **directory containing** the `.duckdb` file (not the file itself):\n\n```python\nimport yaml\nfrom pathlib import Path\n\nwren_home = Path.home() / \".wren\"\nwren_home.mkdir(exist_ok=True)\nprofiles_file = wren_home / \"profiles.yml\"\n\nexisting = (\n    (yaml.safe_load(profiles_file.read_text()) or {}) if profiles_file.exists() else {}\n)\nexisting.setdefault(\"profiles\", {})\n\nprofile_name = \"<source>_dlt\"\nexisting[\"profiles\"][profile_name] = {\n    \"datasource\": \"duckdb\",\n    \"url\": str(Path(\"<duckdb-path>\").resolve().parent),\n    \"format\": \"duckdb\",\n}\nexisting[\"active\"] = profile_name\n\nprofiles_file.write_text(yaml.dump(existing, default_flow_style=False, sort_keys=False))\n```\n\n## Phase 3: Build & Verify — The Project Must Actually Work\n\nThis phase is not optional. A project that generates YAML but fails at query time is not a success.\n\n### Step 1: Build the MDL\n\n```bash\ncd <project-directory>\nwren context build\n```\n\nThis compiles the YAML models into `target/mdl.json`. If this fails, fix the issues before proceeding (see Troubleshooting below).\n\n### Step 2: Validate with a real query\n\nRun at least one query per generated model to confirm the project is functional:\n\n```bash\n# For each model, verify it resolves correctly\nwren --sql 'SELECT COUNT(*) as total FROM \"<table_name>\"'\n```\n\nIf any query fails, debug and fix the model before moving on. Common issues:\n- Wrong catalog in table_reference → \"table not found\"\n- Type mismatch → fix the column type in metadata.yml\n- Missing profile → check `wren profile list`\n\n### Step 3: Run interesting queries\n\nOnce basic queries pass, run 2–3 more interesting queries to show the user what their data looks like:\n\n```bash\n# Preview data\nwren --sql 'SELECT * FROM \"<table_name>\" LIMIT 5'\n\n# If there's a relationship, verify both models are queryable\nwren --sql 'SELECT * FROM \"<parent>\" LIMIT 5'\nwren --sql 'SELECT * FROM \"<child>\" LIMIT 5'\n```\n\nShow the results to the user and explain what they're seeing. This is their first look at the data through Wren — make it count.\n\n### Step 4: Confirm success\n\nOnly after queries return real data, tell the user the setup is complete. Summarize:\n- How many models were created\n- What relationships were detected\n- Which profile is active\n- Example queries they can try next\n\n### Next step: share it as an app\n\nThe project is now DuckDB-backed, which is exactly what GenBI snapshot mode\nwants. If the user wants to turn this data into a shareable dashboard / web app\nand deploy it (Vercel / Cloudflare), hand off to the GenBI workflow:\n`wren skills get genbi`. Its snapshot source is the very `.duckdb` file this\npipeline produced.\n\n## Troubleshooting\n\nIf `wren context build` fails:\n- Check that `data_source: duckdb` is set in `wren_project.yml`\n- Verify the DuckDB file path in the profile is correct\n- Run `wren context validate` for detailed error messages\n\nIf queries fail with \"table not found\":\n- **Most likely cause:** `table_reference.catalog` doesn't match the DuckDB filename. If the file is `pipeline.duckdb`, the catalog must be `pipeline`, not empty string.\n- Check the profile's `url` points to the directory containing the `.duckdb` file\n- Table names with double underscores need quoting: `\"hubspot__contacts\"`\n\nIf queries fail with type errors:\n- Check column types in the model YAML — they should be canonical SQL types (VARCHAR, BIGINT, etc.)\n- Re-run `introspect_dlt.py` with wren SDK installed to get proper type normalization\n\nGeneral:\n- Check that the profile is active: `wren profile list`\n- The DuckDB file might be locked if a dlt pipeline is running — wait for it to finish\n\n## Important notes\n\n- dlt's `_dlt_parent_id` / `_dlt_id` columns are kept in the actual DuckDB tables but hidden from Wren model definitions. They're only used in relationship conditions.\n- DuckDB has a single-writer limitation. Don't run a dlt sync while querying. For concurrent access, dlt should write to a separate file and swap atomically.\n- The generated models use `table_reference` (not `ref_sql`) since they map directly to DuckDB tables created by dlt.\n- Column types are normalized using wren SDK's `parse_type()` with sqlglot's DuckDB dialect. If a type looks wrong, the user can edit the model's `metadata.yml` directly.\n","frontmatter":{"name":"dlt-connector","description":"Connect SaaS data (HubSpot, Stripe, Salesforce, GitHub, Slack, etc.) to Wren Engine for SQL analysis. Guides the user through the full flow: install dlt, pick a SaaS source, set up credentials, run the data pipeline into DuckDB, then auto-generate a Wren semantic project from the loaded data. Use this skill whenever the user mentions: connecting SaaS data, importing data from an API, dlt pipelines, loading HubSpot/Stripe/Salesforce/GitHub/Slack data, querying SaaS data with SQL, or setting up a new data source from a REST API. Also trigger when the user already has a dlt-produced DuckDB file and wants to create a Wren project from it.","license":"Apache-2.0","metadata":{"author":"wrenai"}},"isInternal":false,"tokens":2825,"sizeBytes":11684},{"name":"SKILL.md","path":"core/wren/src/wren/skills_content/enrich-context/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/core/wren/src/wren/skills_content/enrich-context/SKILL.md","title":"enrich-context","category":"anthropic-skill","format":"markdown","content":"---\nname: enrich-context\ndescription: \"Augment a Wren project with business context that DB schema cannot carry — enum value meanings, units (USD vs cents, ms vs sec), NULL semantics, magic sentinels (-1 = unknown), soft-delete default filters, business synonyms, time-grain / TZ conventions, cross-system identifiers, currency rules, canonical-table preferences, AND named aggregation metrics (ARR, churn, DAU, WAU, NRR) proposed as cubes. Runs in one of two modes selected at session start: `grill` (one question at a time, user-driven) or `auto-pilot` (agent infers and applies, escalates only on conflicts and high-blast-radius additions like new cubes / views / relationships). Reads everything under <project>/raw/ (PDFs, glossaries, handbooks, code, data dictionaries) and optionally samples low-cardinality columns from the live DB (grill mode), compares against the current MDL / cubes / knowledge (rules + NL→SQL pairs), then fills gaps via the ten-category gap catalog and the cube proposal flow. Confirmed findings are written back to the right sink. Use when: user says 'enrich context', 'augment my project', 'grill me on this project', 'auto-fill my context', 'agent doesn't understand our docs / enum values / units / null meanings', 'business context is missing', 'what does status=A mean', 'is this amount in USD or cents', 'we keep getting wrong aggregations', 'add cubes for ARR / DAU / churn', 'we have a handbook / glossary / data dictionary the agent should know'; or after generating an MDL and noticing the agent lacks business semantics.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren Enrich Context — Fill the Business-Context Gap\n\nThis skill exists because most business context never lives in a DB schema — it lives in handbooks, glossaries, finance reports, support playbooks, code comments, Slack rules-of-thumb. The agent reads those raw artifacts, finds what's missing from the Wren project, and **either grills the user one question at a time (grill mode) or applies its best inferences directly and hands over an audit (auto-pilot mode)** before writing back. The output lands in the sinks each project already has — MDL, `cubes/`, `knowledge/rules/`, and `knowledge/sql/` — no new artifact, no new tooling.\n\n## Hard rules — READ FIRST\n\n### Universal (apply to both modes)\n\n1. **Only add, never modify existing.** If you find an existing MDL description / relationship / rule that looks wrong, **do not edit it**. Surface it on the \"please fix manually\" list shown in Step 9.\n2. **Every MDL edit must validate.** Right after any MDL YAML change, run `wren context validate`. If it fails, **revert that single change** and feed the error back. Never leave a project in an invalid state.\n3. **Pre-draft every proposal.** Whether you're showing the draft to the user (grill) or applying it directly (auto-pilot), generate the concrete content — never lazy-ask \"what should the description say?\".\n4. **Be explicit about confidence.** In grill mode, open Lane 3 inference questions with \"I'm guessing — \". In auto-pilot, tag every Lane 3 inference and partial Lane 2 match in the Step 9 audit with confidence (high / med / low) and source.\n\n### Grill mode only\n\n5. **One question at a time. Grill relentlessly.** Walk every gap top-down, resolve one decision before moving to the next. Provide a recommended answer for every question.\n6. **Skip is final for this session.** No pending queue, no nagging next round. If the user wants to revisit, they re-run the skill.\n\n### Auto-pilot mode only\n\n7. **Drop into grill for three cases.** Always interrupt auto-pilot and ask the user when:\n   - (a) **Lane 2 conflict** — raw and current MDL disagree.\n   - (b) **High-blast-radius proposal (any lane)** — new cube, new view, new relationship, or new MDL metric/calculated column. These become public artifacts visible to every future agent session, so blast radius doesn't depend on whether the trigger was raw evidence (Lane 2) or inference (Lane 3).\n   - (c) **Lane 2 routing ambiguity** — you can't confidently pick a sink (MDL / `knowledge/rules/` / `knowledge/sql/` / `cubes/`).\n\n   Everything else: apply directly and log to the audit list.\n\n## Step 0 — Mode selection (before anything else)\n\nBefore touching the project or reading any file, ask the user which mode to run in. Lock the choice for the whole session — **no mid-session switching**; the user re-runs to change.\n\n> Two modes for this session:\n>\n> **a) Grill mode** — I walk every gap with you, one question at a time, proposing a draft and waiting for your accept / edit / skip. You stay in the driver's seat. Best when the raw material is sensitive, when you want to learn what I don't know about your project, or when you'd rather review than re-do.\n>\n> **b) Auto-pilot mode** — I read raw + current context, make my best inferences, and apply them. I'll only stop to grill you on (1) conflicts between raw and existing MDL and (2) high-blast-radius additions like new metrics, views, or relationships. The session ends with a full diff + confidence-tagged inference list for you to audit.\n>\n> Which? (a / b)\n\nRemember the choice as `MODE = grill | autopilot` and use it to branch Steps 6 and 9.\n\n## Preflight\n\n### Step 1 — Choose the Wren project\n\n**Always ask the user which project to enrich before doing anything else** — never assume cwd. A user can have several Wren projects and an ambient `~/.wren` profile that doesn't match the one they want to augment.\n\nOffer concrete hints in the question so the user can answer in one round-trip:\n\n```bash\n# Hint 1 — does cwd look like a project?\ntest -f wren_project.yml && pwd\n\n# Hint 2 — does ~/.wren/config.yml point at a default project?\ngrep -E '^project_path:' ~/.wren/config.yml 2>/dev/null\n```\n\nThen ask:\n\n> Which Wren project do you want me to augment?\n> a) `$PWD` (current directory)         ← if Hint 1 matched\n> b) `<path from ~/.wren/config.yml>`   ← if Hint 2 matched\n> c) something else — paste the absolute path\n\nAfter the user answers, lock the path in for the whole session:\n\n```bash\ncd <chosen-path>\ntest -f wren_project.yml || {\n  echo \"Error: <chosen-path> is not a Wren project (no wren_project.yml).\"\n  exit 1\n}\nwren context show >/dev/null 2>&1 || {\n  echo \"Error: wren context show failed — manifest may be invalid.\"\n  exit 1\n}\n```\n\nIf either check fails, stop and tell the user — suggest `wren skills get onboarding` if it's not a project, or `wren context validate` if the manifest is broken.\n\nFrom this point on, **every command and file path in this skill is relative to the chosen project root**. Do not switch projects mid-session — if the user wants to work a different project, end this session and re-run.\n\n### Step 2 — Detect semantic-memory availability\n\n```bash\nwren memory status 2>/dev/null | grep -q \"Backend: lancedb\"\n```\n\nWriting NL→SQL pairs (`wren memory store` → `knowledge/sql/*.md`) works **regardless** — that\nsink is always open. This checks which recall backend is active, i.e. whether the optional\n`memory` extra is installed (LanceDB = semantic; grep = dependency-free fallback):\n\n- Match (`Backend: lancedb`) → set `MEMORY_AVAILABLE = true`. Semantic recall and `wren memory fetch` are usable, and `wren memory index` builds an embedding index in Step 8.\n- No match (grep backend) → set `MEMORY_AVAILABLE = false`. Skip the semantic read/index paths below; pair writeback still happens via `wren memory store`, and grep recall still works.\n\n### Step 3 — Ensure raw/ folder exists\n\nFrom the project root (cwd is already there from Step 1):\n\n```bash\nmkdir -p raw\n```\n\nIf you just created it (the directory was empty or new):\n\n> I've created `raw/` at the project root. Drop anything you think helps explain this project's business context — PDFs, glossaries, handbooks, financial reports, data dictionaries, sample queries, code with comments, screenshots of dashboards, anything.\n>\n> **Heads-up:** the contents may be sensitive. Decide for yourself whether to commit `raw/` to git — I won't touch `.gitignore`.\n>\n> Tell me when you've added the files and I'll start reading.\n\nWait for the user to confirm before continuing.\n\n## Step 4 — Read everything\n\nRead both sides — the raw material and the current Wren context — before forming any opinion.\n\n### Raw\n\nRead every file under `raw/`. Use whatever capability your agent has natively (text, markdown, code, PDF). If you genuinely can't read a particular file, **tell the user once** which file and suggest converting it to text or pasting the relevant excerpt — then move on to the rest. Do **not** install extra Python packages, **do not** reach for new CLI subcommands.\n\n### Current Wren context\n\n| Source | Command |\n|---|---|\n| MDL (full) | `wren context show --output json` |\n| Business rules | `wren context instructions` (reads `knowledge/rules/` + any legacy `instructions.md`) |\n| Existing cubes (names) | `wren cube list` |\n| Existing cubes (measures + dimensions) | `wren cube describe <cube>` for each name above |\n| NL→SQL pairs | read `knowledge/sql/*.md` directly |\n| (Memory) stored pairs | `wren memory list -n 200 --output json` |\n| (Memory) schema as text | `wren memory describe` |\n\nThe memory rows only matter when `MEMORY_AVAILABLE = true`. Reading cubes is essential before any Lane 3 metric proposal — see `cube_proposals` for the duplication guard.\n\n## Step 4.5 — Ground-truth probe (grill mode default; auto-pilot opt-out)\n\nWhen raw is silent on a column's enum / unit / null / magic / time semantics, the catalog's column-local categories (#1, #2, #3, #5, #7 in `gap_catalog`) can often be settled directly by sampling distinct values from the live DB. Read `gap_catalog` before this step — its *Trigger* column tells you which columns are probe candidates.\n\n**Default policy by mode:**\n\n| Mode | Default | How to override |\n|---|---|---|\n| **Grill** | Probe on. Before the first query, ask the user once: \"I want to sample N columns with `LIMIT 30` each to find enum / sentinel / time-grain values — OK?\" Lock the answer for the session. | User says no → skip Step 4.5 entirely; rely on Lane 2 + Lane 3 instead. |\n| **Auto-pilot** | Probe off. The skill never queries the live DB in auto-pilot mode. | None — user must re-run in grill mode if probe would unblock high-confidence inferences. |\n\n**Candidate selection (no DB call yet):**\n\nA column is a probe candidate when *all* hold:\n\n- Description is empty OR does not yet contain the relevant `[tag]` (catalog write format).\n- Column type / name pattern matches catalog #1 (enum), #3 (NULL), #5 (magic), or #7 (time grain).\n- For #3 and #7, the description also lacks event-vs-record or TZ wording.\n\nCategories #2 (unit), #4, #6, #8, #9, #10 are **not** probable — `SELECT DISTINCT` doesn't reveal units, default filters, synonyms, external mappings, currency conventions, or canonical-table preferences. Those need raw evidence or human judgment.\n\n**Probe query:**\n\n```bash\nwren --sql \"SELECT DISTINCT <col> FROM <model> LIMIT 30\" --output json\n# For magic sentinels (catalog #5), also fetch min/max:\nwren --sql \"SELECT MIN(<col>) AS lo, MAX(<col>) AS hi FROM <model>\" --output json\n```\n\n- **≤ 30 distinct values returned** → enum / sentinel / grain candidate. Draft the `[tag]` line and surface to user (grill) with confidence \"med — probed values, semantics still inferred\".\n- **30 returned (LIMIT hit)** → cardinality too high; not an enum / sentinel candidate. Skip.\n- **Query fails** (permissions, connection, large-table timeout) → do not retry. Log the failure to the audit list, surface in Step 9, continue with Lane 2 + Lane 3 only.\n\n**Safety:**\n\n- Probe each (model, column) at most once per session.\n- Never probe a column that already has a matching `[tag]` line — Universal Rule 1.\n- Probe results stay in working memory; do not write them to disk.\n\n## Step 5 — Three gap-detection lanes (in your head, no artifact)\n\nHold all three lanes in working memory. Do not write a `gaps.yml`.\n\nBefore sweeping, load `gap_catalog` — the ten business-semantic categories the schema cannot carry. Each lane consumes the catalog differently: Lane 1 walks it as type-aware mechanical triggers, Lane 2 classifies each atomic raw claim into one of the 10 categories before routing, Lane 3 seeds inference prompts when a trigger fires but raw is silent.\n\n### Lane 1 — Structural coverage (mechanical)\n\nScan the current MDL and check:\n\n- Every model has a non-empty `properties.description`?\n- Every column has a description (at least for non-PK, non-FK ones)?\n- Every model has a `primary_key`?\n- Every model has at least one relationship (orphan models are suspicious)?\n- `knowledge/rules/` has real content beyond the scaffold default?\n- `knowledge/sql/` has at least a few canonical NL→SQL pairs?\n\nPlus, walk every column / model against `gap_catalog` triggers:\n\n- For each column matching catalog #1 / #2 / #3 / #5 / #7 triggers → is the corresponding `[tag]` line present in `properties.description`?\n- For each model with a soft-delete column (`deleted_at`, `is_active`, `archived_at`, etc.) → is there a `## Default filters` rule in `knowledge/rules/` covering it (catalog #4)?\n- For each lookalike table pair (e.g. `users` / `users_v3`) → is there a `## Canonical tables` rule (catalog #10)?\n- For each `*_currency` / `fx_rate` / external-system ID column → is the matching `## Currency` (#9) or `## External identifiers` (#8) section present?\n- Business terms in `knowledge/rules/` or raw that don't map verbatim to model / column names → catalog #6 `## Naming conventions` rule missing.\n\nEach unsatisfied check is a candidate. Combine with Step 4.5 probe results (if available) before moving to Lane 2.\n\n### Lane 2 — Claim-diff (raw vs current context)\n\nFor each raw file, internally extract 5–15 **atomic claims** — single statements that could be true or false, e.g. \"an order has exactly one customer\", \"user means type=default by default\", \"ARR equals MRR × 12 minus refunds\". Then for each claim, classify against the current Wren context:\n\n| Class | Meaning | Resolution outcome |\n|---|---|---|\n| **covered** | already reflected in MDL / instructions / pairs | skip |\n| **partial** | the topic exists but the wording / scope differs | propose tightening |\n| **new** | nothing in current context matches | route to a sink |\n| **conflict** | raw says A, current context says B | grill the user (both modes), **but do not edit existing** — surface for manual fix |\n\n### Lane 3 — Inference (your own guesses)\n\nAfter reading raw and the current MDL, propose additions the user did **not** literally state in raw but that would clearly help the agent later. Examples:\n\n- \"I see `quarterly_churn` referenced five times in `finance.pdf`. No existing cube covers it. Want me to add `cubes/quarterly_churn/metadata.yml` with measure = `COUNT(*) FILTER (WHERE churned_at IS NOT NULL) / NULLIF(COUNT(*), 0)`?\" — see `cube_proposals` for the YAML template and duplication guard.\n- \"Your support handbook keeps mentioning `core users` without defining it. Is this `users WHERE tier = 'premium'`? Want me to make a view?\"\n- \"The data dictionary says `events.payload` is JSON but the column has no description — let me draft one.\"\n\nFor any aggregation-shaped proposal (`SUM`, `COUNT`, `AVG`, \"by month / by status / per customer\" patterns), **default to a cube**. Run `wren cube list` + `wren cube describe` first to confirm no existing cube already covers the measure expression; if one does, skip the proposal and add a `knowledge/sql/` example pointing at the existing cube instead. The full decision tree, naming rules, and validation flow live in `cube_proposals`.\n\n**In grill mode, open every Lane 3 question with \"I'm guessing — \".** In auto-pilot, tag the audit entry with `agent inference` so the user sees you extrapolated.\n\n## Step 6 — Resolve gaps\n\nBranch on the `MODE` locked in Step 0.\n\n### Grill mode\n\nUse this conversational pattern for every gap surfaced in Lanes 1–3:\n\n> Interview the user relentlessly about every gap until we reach a shared understanding. Walk down each branch of the decision tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.\n>\n> Ask the questions one at a time.\n>\n> If a question can be answered by exploring the codebase or the raw files, do that instead of asking.\n\nFor every grill turn:\n\n1. State the gap and where it came from (Lane 1 / 2 / 3, and for Lane 2 quote the raw file + a short excerpt).\n2. Propose the concrete answer — draft the description, the rule, the SQL pair, the relationship.\n3. **Propose the sink** (\"I'll add this to `knowledge/rules/` as a rule\" / \"I'll add this to the `users` model description in MDL\").\n4. Let the user accept / edit / skip.\n5. On accept: write back (Step 7).\n6. On edit: apply their wording, then write back.\n7. On skip: drop it, move to the next gap. Do not requeue.\n\nWhen the user gives a curve-ball answer (\"actually we don't track that\") — pivot. The goal is shared understanding, not pushing a pre-built list.\n\n### Auto-pilot mode\n\nProcess every finding from Lanes 1–3 directly — **except** for the three escalation cases from Universal Rule 7:\n\n- **Lane 2 conflict** — drop into grill for this single question, never auto-resolve.\n- **Lane 3 proposing a new metric / view / relationship** — drop into grill before applying.\n- **Lane 2 routing ambiguous** — drop into grill for the sink choice only, then auto-apply.\n\nFor everything else (Lane 1 mechanical fixes, Lane 2 unambiguous new claims, Lane 3 low-impact description tweaks):\n\n1. Synthesize the concrete proposal (description / rule / SQL pair) using your best inference.\n2. Decide the sink using the routing table (Step 7).\n3. Write back.\n4. Run `wren context validate` immediately after any MDL edit. On failure: revert the single change and log the revert with the error.\n5. Append to the audit list: the finding, the sink, confidence tag (high / med / low), and source (raw file + excerpt for Lane 2, `agent inference` for Lane 3, `structural` for Lane 1).\n\nAuto-pilot does not pause for confirmation on each item — the user reviews the full diff + audit list in Step 9. They are the reviewer, not the gatekeeper.\n\n## Step 7 — Routing & writeback\n\nDecide the sink as part of the proposal (Step 6.3 in grill mode; Step 6.2 in auto-pilot), so the user can correct routing in grill mode and audit it in auto-pilot.\n\n| Finding type | Sink | How to write |\n|---|---|---|\n| Schema structure / relationship / view / model or column description | **MDL YAML** under `models/`, `views/`, `relationships.yml` | Edit the YAML file directly. For catalog #1 / #2 / #3 / #5 / #7 / PII, append a `[tag]` line to `properties.description` (prose first, then one tag per category). See `gap_catalog` for the exact tag format and triggers. |\n| Aggregation metric / named measure (with measures + dimensions) | **`cubes/<name>/metadata.yml`** | New file per cube. Default sink for any `SUM` / `COUNT` / `AVG` / ratio metric raw defines or Lane 3 infers. See `cube_proposals` for the YAML template, naming policy, duplication guard, and validation flow. Run `wren context validate` + `wren cube query --cube <name> --sql-only` after writing; revert on either failure. **Always escalates to grill in auto-pilot** (Universal Rule 7b). |\n| Default filter / implicit rule / business convention / naming convention / external mapping / currency / canonical table | **`knowledge/rules/`** | Append under the catalog-specified `##` section heading (#4 → `## Default filters`, #6 → `## Naming conventions`, #8 → `## External identifiers`, #9 → `## Currency`, #10 → `## Canonical tables`) inside a topic file under `knowledge/rules/` (e.g. `knowledge/rules/conventions.md`). Create the file/heading if absent; never modify existing text. |\n| NL→SQL example (canonical or ad-hoc) | **`knowledge/sql/`** | `wren memory store --nl \"...\" --sql \"...\" --tags \"source:enrich\"` — writes `knowledge/sql/<slug>.md` (committable) and indexes it when the extra is present. Works whether or not `MEMORY_AVAILABLE`. |\n\nCatalog-driven routing means every column-local proposal goes to the column's `properties.description` with a `[tag]` line; every cross-model rule goes to `knowledge/rules/` under a fixed heading. This keeps re-enrichment deterministic (greppable) and avoids inventing new sink locations.\n\n### After every MDL edit\n\n```bash\nwren context validate\n```\n\nIf it fails:\n1. Revert the single change you just made.\n2. Show the user the validation error (grill mode) or log it in the audit (auto-pilot).\n3. In grill mode, re-grill on that specific gap with the error as new context.\n4. In auto-pilot, mark the finding as \"revert: validation failed\" and move on.\n\n### Format reminders\n\n- NL→SQL pairs are written by `wren memory store`; each becomes a `knowledge/sql/<slug>.md` with YAML frontmatter (`nl`, `sql`, `source`, optional `datasource`/`tags`). Don't hand-write the files — use `wren memory store --tags \"source:enrich\"`.\n- MDL YAML uses snake_case keys (e.g. `primary_key`, `is_calculated`, `not_null`). `wren context build` converts to camelCase for `target/mdl.json`.\n- `knowledge/rules/` holds free-form markdown, one file per topic. Group rules by topic with `##` headings.\n\n## Step 8 — Session finalize\n\nAfter Step 6 ends (user says stop in grill mode, or every finding is processed in auto-pilot):\n\n```bash\nwren context build\n```\n\nThis recompiles `target/mdl.json` from the YAML edits.\n\nIf `MEMORY_AVAILABLE = true`:\n\n```bash\nwren memory index\n```\n\nThis rebuilds the index from the MDL schema items and the `knowledge/sql/` pairs. (Business\nrules in `knowledge/rules/` are read by `wren context instructions`, not embedded by `index`.)\n\n## Step 9 — Summary\n\n### Both modes — common section\n\nPrint a tight session report:\n\n```text\nWren Enrich Context — session summary  (mode: <grill|autopilot>)\n\nAdded:\n  MDL              : N model descriptions, N column descriptions, N relationships, N views\n                     by tag: [enum]=N [unit]=N [null]=N [magic]=N [time]=N [pii]=N\n  cubes            : N new (names: <list>)                                    via cubes/<name>/metadata.yml\n  knowledge/rules/ : N new rules across sections\n                     by section: Default filters=N | Naming conventions=N | External identifiers=N | Currency=N | Canonical tables=N\n  knowledge/sql/   : N new NL→SQL pairs                                       via wren memory store\n  Probe            : N columns sampled, M failed                             (grill mode only)\n\nPlease fix manually (we don't edit existing fields):\n  - models/orders/metadata.yml: existing description seems to contradict raw/glossary.pdf p.3\n  - relationships.yml: existing orders↔customers is MANY_TO_ONE but raw/data_dict.md p.7 says MANY_TO_MANY\n  - …\n```\n\n### Grill mode extras\n\nAppend:\n\n```text\nSkipped this session: N gaps (re-run /wren-enrich-context to revisit)\n```\n\n### Auto-pilot mode extras\n\nAppend a detailed audit so the user can sanity-check inferences:\n\n```text\nInferred items (please review):\n  high   | MDL model:orders.description            | from raw/glossary.pdf p.2 — \"Order = ...\"\n  high   | knowledge/rules/ rule                   | from raw/handbook.md §4 — \"default tier ...\"\n  med    | MDL column:users.signup_source.desc     | agent inference from raw/onboarding.md\n  low    | knowledge/sql/: \"weekly active customers\" | agent inference, no direct raw evidence\n\nValidation:\n  K successful applies, M reverted after wren context validate failed:\n    - relationships.yml: <error> → reverted\n\nEscalated to grill (raw vs MDL conflicts / high-impact additions):\n  - <count> items — see grill transcript above\n```\n\nThe user should be encouraged to skim the audit and either accept it as-is, manually tweak low-confidence rows, or re-run in grill mode if they want to revisit interactively.\n\n## Things to avoid\n\n- Do not write a `gaps.yml`, `state.yml`, or any other tracking artifact. The session lives entirely in conversation.\n- Do not modify any existing MDL field, `knowledge/rules/` rule, or `knowledge/sql/` pair — only append / add. Surface mismatches on the manual-fix list.\n- Do not install new Python packages (`pypdf`, `docling`, …) to read raw. Use what your agent already has; ask the user to convert files you can't open.\n- Do not auto-resolve a conflict between raw and current MDL — always grill the user, in **both** modes.\n- Do not present Lane 3 inferences as if they were quoted from raw. Open with \"I'm guessing — \" (grill) or tag `agent inference` (auto-pilot).\n- `wren memory store` works whether or not `MEMORY_AVAILABLE` — it always writes the `knowledge/sql/*.md` pair (and indexes it only when the extra is present). No need to fall back to another sink.\n- Do not commit anything to git. The user owns the commit decision.\n- Do not nag about skipped questions. Skip is skip for this session (grill mode only — auto-pilot has no skip concept).\n- Do not run `wren context build` after every single MDL edit — once at the end is enough. Do run `wren context validate` after every edit.\n- Do not assume `raw/` was created by `wren context init` — it isn't. This skill creates it.\n- Do not switch modes mid-session. The user re-runs to change mode.\n- Do not append a `[tag]` line if the same category tag already exists for that column — Universal Rule 1. Surface contradictions on the manual-fix list instead.\n- Do not invent new `knowledge/rules/` section headings. Stick to the five catalog-defined headings (`## Default filters`, `## Naming conventions`, `## External identifiers`, `## Currency`, `## Canonical tables`). Anything that doesn't fit goes on the manual-fix list.\n- Do not probe the live DB in auto-pilot mode. Step 4.5 is grill-only by default.\n- Do not propose a cube whose measure expression already exists in another cube on the same `base_object` — write a `knowledge/sql/` example pointing at the existing cube instead. See `cube_proposals` duplication guard.\n- Do not modify an existing cube YAML even when raw contradicts it — Universal Rule 1. Surface on the manual-fix list.\n- Do not write a new cube alongside an old MDL `metrics:` entry that already covers the same logic. Surface as \"consider migrating to cube\" on the manual-fix list.\n- Do not skip `wren cube query --cube <name> --sql-only` after creating a cube. Structural `wren context validate` doesn't catch unresolvable measure / dimension expressions.\n- In auto-pilot, do not auto-apply Lane 2 conflicts or new metric / view / relationship inferences — always drop into grill for those.\n\n## See also\n\n- `gap_catalog` — the ten business-semantic gap categories, with triggers, default sinks, and write formats. Read this before Step 4.5 and Step 5.\n- `cube_proposals` — decision tree for when to propose a cube vs view vs calculated column, the cube YAML template, naming policy, duplication guard, and validation flow. Read this before any Lane 3 aggregation-shaped proposal.\n","frontmatter":{"name":"enrich-context","description":"Augment a Wren project with business context that DB schema cannot carry — enum value meanings, units (USD vs cents, ms vs sec), NULL semantics, magic sentinels (-1 = unknown), soft-delete default filters, business synonyms, time-grain / TZ conventions, cross-system identifiers, currency rules, canonical-table preferences, AND named aggregation metrics (ARR, churn, DAU, WAU, NRR) proposed as cubes. Runs in one of two modes selected at session start: `grill` (one question at a time, user-driven) or `auto-pilot` (agent infers and applies, escalates only on conflicts and high-blast-radius additions like new cubes / views / relationships). Reads everything under <project>/raw/ (PDFs, glossaries, handbooks, code, data dictionaries) and optionally samples low-cardinality columns from the live DB (grill mode), compares against the current MDL / cubes / knowledge (rules + NL→SQL pairs), then fills gaps via the ten-category gap catalog and the cube proposal flow. Confirmed findings are written back to the right sink. Use when: user says 'enrich context', 'augment my project', 'grill me on this project', 'auto-fill my context', 'agent doesn't understand our docs / enum values / units / null meanings', 'business context is missing', 'what does status=A mean', 'is this amount in USD or cents', 'we keep getting wrong aggregations', 'add cubes for ARR / DAU / churn', 'we have a handbook / glossary / data dictionary the agent should know'; or after generating an MDL and noticing the agent lacks business semantics.","license":"Apache-2.0","metadata":{"author":"wrenai"}},"isInternal":false,"tokens":6599,"sizeBytes":27142},{"name":"SKILL.md","path":"core/wren/src/wren/skills_content/genbi/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/core/wren/src/wren/skills_content/genbi/SKILL.md","title":"genbi","category":"anthropic-skill","format":"markdown","content":"---\nname: genbi\ndescription: \"Turn a Wren project's context layer into a shareable, browser-side GenBI web app and deploy it to the user's Vercel or Cloudflare account. Orchestrates the full flow: `wren genbi build` returns a project-hydrated build instruction, the agent authors the app from scratch into apps/<name>/, then register → verify → deploy produce a shareable URL. Use this skill whenever the user wants to: build a dashboard from their Wren project, make a shareable analytics app, deploy their context layer as a web app, host a GenBI app on Vercel or Cloudflare Pages, or asks for a 'genbi app'.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren GenBI App — Agent Workflow Guide\n\n> This guide is served by the `wren` CLI (`wren skills get genbi`), so it\n> always matches your installed wrenai version.\n\nTurn a Wren context layer into a shareable GenBI app — from a natural-language\nrequest to a public URL in one conversation.\n\n**Division of labor:** the CLI owns the authoritative build instruction (it\nknows the live project facts and the pinned `wren-core-wasm` version) and all\ndeterministic state (index, verify, deploy). You — the agent — author the app\ncode by following the instruction. Never hand-write `.wren/apps.yml`.\n\n## Preconditions\n\n1. A Wren project is discoverable (`wren_project.yml` in cwd/ancestors, or ask\n   for the path and pass `-p`).\n2. The context layer exists. If `target/mdl.json` is missing, `wren genbi\n   build` compiles it implicitly — no separate step needed.\n3. `wren` CLI ≥ the version that ships the `genbi` command group\n   (`wren genbi --help` works).\n\n## Workflow\n\n### 1. Resolve the app name and data mode\n\n- App name: short kebab-case derived from the request (e.g. `sales-overview`).\n- Data mode:\n  - `snapshot` (default) — data is bundled with the app as parquet/duckdb and\n    queried client-side. Fully serverless. Right for demos, reports, small\n    data, and dlt-pipeline output.\n  - `live` — the app calls back to the user's warehouse/API at view time.\n    Right for production-scale or always-fresh data. Requires a CORS-enabled\n    endpoint and carries strict no-credentials rules.\n- Ask the user ONLY if the choice is genuinely ambiguous.\n\n### 2. Get the build instruction\n\n```bash\nwren genbi build <name> --prompt \"<the user's request, verbatim>\" --data-mode <mode>\n```\n\nFor long or multi-line prompts use `--prompt-file <file>` or pipe to\n`--prompt -`. The command prints the authoritative build instruction —\nwasm wiring (pinned version, CDN load), the project's model/column inventory,\ndata-mode guidance, acceptance criteria, and the target folder. It writes no\napp files — the only thing it may touch is `target/mdl.json`, which it compiles\nfirst if missing (see Precondition 2).\n\n### 3. Author the app\n\nFollow the instruction exactly. Key conventions:\n\n- Write everything under `apps/<name>/` — never outside it.\n- Copy the compiled MDL into the app as `apps/<name>/mdl.json`.\n- Load `wren-core-wasm` from the CDN given in the instruction; never bundle\n  the ~68MB binary.\n- snapshot: export the data the dashboard needs into `apps/<name>/data/` as\n  parquet (`verify` requires at least one `.parquet`/`.duckdb` asset). See\n  **Snapshot data export** below for the recipe and where the data comes from.\n- live: write an endpoint-only connection config. NEVER inline credentials —\n  `verify` scans for them (best-effort) and `deploy` gates on `verify`, but\n  the rule is on you: a public static host exposes every shipped file.\n- Design the dashboard to actually answer the user's request: pick the right\n  charts/tables for the question, not a generic template.\n\n#### Snapshot data export\n\nThe CLI hands you the build instruction, but the snapshot bytes still have to\nbe exported into the app folder — that step is yours.\n\n**Where the data comes from:**\n\n- **DuckDB-backed project** (incl. anything loaded by the `dlt-connector`\n  skill — its pipelines always land in a `.duckdb` file): the project's DuckDB\n  file *is* your snapshot source. If the user is connecting SaaS data (HubSpot,\n  Stripe, Salesforce, …) and has no project yet, run the SaaS→project flow\n  first: `wren skills get dlt-connector`. Then come back here to ship it.\n- **Warehouse-backed project** (Postgres, BigQuery, Snowflake, …): run the\n  query/queries the dashboard needs through the MDL layer and write the result\n  to parquet. Keep snapshots small — snapshot mode is for demos/reports, not\n  full warehouse extracts; use `live` mode for large or always-fresh data.\n\n**Recipe (DuckDB → parquet):**\n\n```bash\n# from the project root; <db> is the project's DuckDB file (see wren_project.yml)\npython - <<'PY'\nimport duckdb\ncon = duckdb.connect(\"<db>.duckdb\", read_only=True)\ncon.execute(\n    \"COPY (SELECT * FROM <table>) \"\n    \"TO 'apps/<name>/data/<table>.parquet' (FORMAT parquet)\"\n)\nPY\n```\n\nOnly export the columns/rows the dashboard uses. The compiled `mdl.json` you\ncopied in keeps the context layer intact regardless of how you bundle data.\n\n### 4. Register and verify\n\n```bash\nwren genbi register <name> --data-mode <mode>\nwren genbi verify <name>\n```\n\nIf verify fails: fix the reported problems and re-run verify. Do NOT proceed\nto deploy on a failed verify. Offer `wren genbi open <name>` for a local\npreview before shipping.\n\n### 5. Deploy (only if the user asked for it)\n\n```bash\nwren genbi deploy <name> --provider vercel      # or cloudflare\n```\n\n- Preview deployment by default. **Confirm with the user before `--prod`.**\n- Tokens: the CLI discovers `VERCEL_TOKEN` / `CLOUDFLARE_API_TOKEN` from the\n  environment or `.env` files. If missing, ask the user to export it or add\n  it to the project `.env`. NEVER put a token on the command line.\n- Cloudflare also needs `CLOUDFLARE_ACCOUNT_ID` (env or `.env`), a token\n  scoped with Pages:Edit, and the `wrangler` CLI on PATH (or `npx` available)\n  — the adapter shells out to `wrangler pages deploy`. If it's missing, ask\n  the user to `npm install -g wrangler`.\n- Report the returned URL to the user. Re-deploying the same app updates the\n  same provider target.\n- **Verify the URL actually loads.** After deploying, fetch the URL — a\n  successful deploy can still return **HTTP 401/403** to outsiders because of\n  the provider's access protection (see below). Don't report a link as\n  \"shareable\" until you've confirmed it serves.\n\n### Vercel Deployment Protection (the 401 trap)\n\nNew Vercel projects ship with **Vercel Authentication** turned ON by default,\nso every deployment — preview *and* production — returns **401** to anyone not\nlogged into the owning Vercel account/team. The deploy itself succeeded; the\nURL is just gated.\n\n- To make the app publicly shareable, the user disables it in the Vercel\n  dashboard: **Project → Settings → Deployment Protection → Vercel\n  Authentication → Disabled** (or scope it to production only). This setting\n  is not controllable from `wren genbi deploy`; it's a one-time toggle per\n  project in Vercel.\n- If the user only needs a private link (viewable while logged into their\n  Vercel account), leaving protection on is fine — just tell them the link\n  won't work for logged-out visitors.\n\n## Safety boundaries\n\n- Never inline secrets/credentials into app files — the deploy target is a\n  public static host; anyone with the URL can read every file. `verify`\n  scans for inlined credentials, but treat it as best-effort\n  defense-in-depth, not a guarantee — never rely on it to catch a secret\n  you shouldn't have written in the first place.\n- Never pass tokens as CLI flags; they leak into shell history.\n- Confirm before production deploys.\n- All index state goes through `wren genbi register/remove` — never edit\n  `.wren/apps.yml` by hand.\n\n## Quick reference\n\n| Step | Command |\n| --- | --- |\n| Get build instruction | `wren genbi build <name> --prompt \"…\" [--data-mode snapshot\\|live]` |\n| Record the app | `wren genbi register <name> --data-mode <mode>` |\n| Preflight | `wren genbi verify <name>` |\n| Local preview | `wren genbi open <name>` |\n| Ship | `wren genbi deploy <name> --provider vercel\\|cloudflare [--prod]` |\n| Inventory | `wren genbi list` / `wren genbi remove <name>` |\n","frontmatter":{"name":"genbi","description":"Turn a Wren project's context layer into a shareable, browser-side GenBI web app and deploy it to the user's Vercel or Cloudflare account. Orchestrates the full flow: `wren genbi build` returns a project-hydrated build instruction, the agent authors the app from scratch into apps/<name>/, then register → verify → deploy produce a shareable URL. Use this skill whenever the user wants to: build a dashboard from their Wren project, make a shareable analytics app, deploy their context layer as a web app, host a GenBI app on Vercel or Cloudflare Pages, or asks for a 'genbi app'.","license":"Apache-2.0","metadata":{"author":"wrenai"}},"isInternal":false,"tokens":2094,"sizeBytes":8226},{"name":"SKILL.md","path":"core/wren/src/wren/skills_content/generate-mdl/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/core/wren/src/wren/skills_content/generate-mdl/SKILL.md","title":"generate-mdl","category":"anthropic-skill","format":"markdown","content":"---\nname: generate-mdl\ndescription: \"Generate a Wren MDL project by exploring a database with available tools (SQLAlchemy, database drivers, MCP connectors, or raw SQL). Guides agents through schema discovery, type normalization, and MDL YAML generation using the wren CLI. Use when: user wants to create or set up a new MDL, onboard a new data source, or scaffold a project from an existing database.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Generate Wren MDL — CLI Agent Workflow\n\nBuilds an MDL project by discovering database schema and converting it\ninto Wren's YAML project format. The agent uses whatever database tools\nare available in its environment for introspection; the wren CLI handles\ntype normalization, validation, and build.\n\nFor memory and query workflows after setup, run `wren skills get usage`.\n\n## Prerequisites\n\n- `wren` CLI installed (`pip install \"wrenai[<datasource>]\"`)\n- A working database connection (credentials available to the agent)\n- A connection profile (set up via `wren profile add`) or connection info ready\n\n## Alternative — import from an OpenMetadata catalog\n\nIf the user's team already catalogs this database in OpenMetadata, import\nfrom the catalog instead of introspecting the live database. The import\ncarries curated table/column descriptions, tags, owners, and foreign-key\nconstraints (as `relationships.yml`), so the join-inference work in Phase 4\nis mostly done:\n\n```bash\nwren context import openmetadata --host <om_url> --token <om_token> \\\n  --service <om_service> --database <db> --schema <schema> --dry-run\nwren context import openmetadata --host <om_url> --token <om_token> \\\n  --service <om_service> --database <db> --schema <schema>\n```\n\nAsk the user for the OpenMetadata URL, a read-only bot or personal access\ntoken, and the service name as it appears in OpenMetadata. `--table`\n(repeatable) narrows the import. Without `--force` the command only adds\nfiles that do not exist and lists the collisions it left untouched; `--force`\nregenerates from OpenMetadata and discards local edits to generated files —\nconfirm with the user before using it. Relationships come from foreign keys\nonly; review whether additional joins are needed (Phase 4), then bind a\nprofile and finish with Phase 5 (`wren context validate` → `wren context build`).\n\n## Phase 0 — Detect existing project\n\n**Goal:** If the current directory is already inside a wren project, let the user decide how to proceed.\n\nCheck whether `wren_project.yml` exists in the current working directory\n(or any parent up to the repository root). If found:\n\n1. Tell the user that an existing wren project was detected and show its path.\n2. Ask:\n   - **Reset** — wipe the existing project (`models/`, `views/`,\n     `relationships.yml`, `knowledge/`, and rebuild `wren_project.yml`)\n     and regenerate from scratch in the same directory.\n   - **New path** — keep the existing project untouched and choose a\n     different directory for the new project. Ask the user for the new path,\n     then `wren context init --path <new_path>` and continue from Phase 1\n     using that path.\n\nIf no existing project is detected, proceed directly to Phase 1.\n\n## Phase 1 — Establish connection and scope\n\n**Goal:** Confirm the agent can reach the database and agree on scope with the user.\n\n1. Verify connectivity using whichever tool is available:\n   - If SQLAlchemy: `engine.connect()` test\n   - If database driver: simple query like `SELECT 1`\n   - If wren profile exists: `wren profile debug` to check config\n   - If raw SQL via wren: `wren --sql \"SELECT 1\"` (requires profile or connection file)\n\n2. Ask the user:\n   - Which **schema(s)** or **dataset(s)** to include (skip if only one exists)\n   - Whether to include **all tables** or a subset\n   - The **datasource type** for wren (e.g., `postgres` (including Aurora), `mysql` (including Aurora), `bigquery`, `snowflake`) — needed for type normalization dialect\n\n## Phase 2 — Discover schema\n\n**Goal:** Collect table names, column names, column types, and constraints.\n\nUse whatever introspection method is available. Here are common approaches\nranked by convenience:\n\n### Option A: SQLAlchemy (recommended if available)\n\n```python\nfrom sqlalchemy import create_engine, inspect\n\nengine = create_engine(connection_url)\ninspector = inspect(engine)\n\ntables = inspector.get_table_names(schema=\"public\")\n\nfor table in tables:\n    columns = inspector.get_columns(table, schema=\"public\")\n    # columns → [{\"name\": \"id\", \"type\": INTEGER(), \"nullable\": False, ...}]\n\n    pk = inspector.get_pk_constraint(table, schema=\"public\")\n    # pk → {\"constrained_columns\": [\"id\"], \"name\": \"orders_pkey\"}\n\n    fks = inspector.get_foreign_keys(table, schema=\"public\")\n    # fks → [{\"constrained_columns\": [\"customer_id\"],\n    #          \"referred_table\": \"customers\",\n    #          \"referred_columns\": [\"id\"]}]\n```\n\n### Option B: Database-specific driver\n\n- **psycopg / asyncpg (Postgres):** Query `information_schema.columns` and `information_schema.table_constraints`\n- **google-cloud-bigquery:** `client.list_tables()`, `client.get_table()` → `table.schema`\n- **snowflake-connector-python:** `SHOW COLUMNS IN TABLE`, `SHOW PRIMARY KEYS IN TABLE`\n- **clickhouse-driver:** `DESCRIBE TABLE`, `system.tables`\n\n### Option C: Raw SQL via wren\n\nIf no driver is available but a wren profile is configured, query\n`information_schema` through wren itself:\n\n```bash\nwren --sql \"SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'\" -o json\nwren --sql \"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'orders'\" -o json\n```\n\nNote: this goes through the MDL layer, so it only works if you already\nhave a minimal MDL or if the database supports `information_schema` as\nregular tables. For bootstrapping from zero, Option A or B is preferred.\n\n## Phase 3 — Normalize types\n\n**Goal:** Convert raw database types to wren-core-compatible types.\n\n### Python import (recommended for batch processing)\n\n```python\nfrom wren.type_mapping import parse_type, parse_types\n\n# Single type\nnormalized = parse_type(\"character varying(255)\", \"postgres\")  # → \"VARCHAR(255)\"\n\n# Batch — entire table at once\ncolumns = [\n    {\"column\": \"id\", \"raw_type\": \"int8\"},\n    {\"column\": \"name\", \"raw_type\": \"character varying\"},\n    {\"column\": \"total\", \"raw_type\": \"numeric(10,2)\"},\n]\nnormalized_cols = parse_types(columns, dialect=\"postgres\")\n# Each dict now has a \"type\" key with the normalized value\n```\n\n### CLI (if Python import not available)\n\nSingle type:\n```bash\nwren utils parse-type --type \"character varying(255)\" --dialect postgres\n# → VARCHAR(255)\n```\n\nBatch (stdin JSON):\n```bash\necho '[{\"column\":\"id\",\"raw_type\":\"int8\"},{\"column\":\"name\",\"raw_type\":\"character varying\"}]' \\\n  | wren utils parse-types --dialect postgres\n```\n\n## Phase 4 — Scaffold and write MDL project\n\n**Goal:** Create the YAML project structure.\n\n### Step 1 — Initialize project\n\n```bash\nwren context init --path /path/to/project\n```\n\nThis creates:\n```text\nproject/\n├── wren_project.yml\n├── models/              # business-facing tables/models\n├── views/               # named SQL statements\n├── cubes/               # pre-aggregation cubes (measures + dimensions)\n├── relationships.yml\n└── knowledge/           # business rules (rules/) + NL→SQL pairs (sql/)\n```\n\n> **When to define cubes:** If the user asks aggregation questions like\n> \"revenue by month\" or \"top customers\", define cubes alongside models —\n> they give agents a structured query API instead of forcing them to\n> hand-write `GROUP BY` / `DATE_TRUNC` SQL. See the\n> [Cube guide](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/cubes.md).\n\n> **IMPORTANT: `catalog` and `schema` in `wren_project.yml`**\n>\n> These are Wren Engine's internal namespace — they are NOT the database's\n> native catalog or schema. Keep the defaults (`catalog: wren`, `schema: public`)\n> unless you are intentionally configuring a multi-project namespace.\n>\n> Your database's actual catalog/schema is specified per-model in `table_reference`\n> (see Step 2). Do not copy database catalog/schema values into `wren_project.yml`.\n\n### Step 2 — Write model files\n\nFor each table, create a YAML file under `models/`. Use snake_case\nnaming (the build step converts to camelCase automatically).\n\n```yaml\n# models/orders/metadata.yml\nname: orders\ntable_reference:\n  catalog: \"\"           # database catalog (empty string if not applicable;\n                        #   for DuckDB, use the DB file name without extension,\n                        #   e.g. jaffle_shop.duckdb → catalog: jaffle_shop)\n  schema: public        # database schema (this IS the DB schema)\n  table: orders         # database table name\nprimary_key: order_id\ncolumns:\n  - name: order_id\n    type: INTEGER\n    not_null: true\n  - name: customer_id\n    type: INTEGER\n  - name: total\n    type: \"DECIMAL(10, 2)\"\n  - name: status\n    type: VARCHAR\n    properties:\n      description: \"Order status: pending, shipped, delivered, cancelled\"\n```\n\n### Step 3 — Write relationships\n\nFrom foreign key constraints discovered in Phase 2:\n\n```yaml\n# relationships.yml\nrelationships:\n  - name: orders_customers\n    models:\n      - orders\n      - customers\n    join_type: many_to_one\n    condition: \"orders.customer_id = customers.customer_id\"\n```\n\nJoin type mapping:\n- FK table → PK table: `many_to_one`\n- PK table → FK table: `one_to_many`\n- Unique FK: `one_to_one`\n- Junction table: `many_to_many`\n\nIf no foreign keys were found, infer from naming conventions:\n- Column `<table>_id` or `<table_singular>_id` → likely FK to `<table>`\n- Ask the user to confirm inferred relationships\n\n### Step 4 — Add descriptions (optional but valuable)\n\nAsk the user to describe:\n- Each model (1-2 sentences about what the table represents)\n- Key columns (especially calculated fields or non-obvious names)\n\nThese descriptions are indexed by `wren memory index` and significantly\nimprove LLM query accuracy.\n\n## Phase 5 — Validate and build\n\n```bash\n# Validate YAML structure and integrity\nwren context validate --path /path/to/project\n\n# If strict mode is desired:\nwren context validate --path /path/to/project --strict\n\n# Build JSON manifest\nwren context build --path /path/to/project\n\n# Verify against database\nwren --sql \"SELECT * FROM <model_name> LIMIT 1\"\n```\n\nIf validation fails, fix the reported issues and re-run. Common errors:\n- Duplicate model/column names\n- Missing primary key\n- Relationship referencing non-existent model\n- Invalid column type (try re-running through `parse_type`)\n\n## Phase 6 — Initialize memory\n\n```bash\n# Index schema (generates seed NL-SQL examples automatically)\nwren memory index\n\n# Verify\nwren memory status\n```\n\nAfter this step, `wren memory fetch` and `wren memory recall` are\noperational. See `wren skills get usage` for query workflows.\n\n## Phase 7 — Iterate with the user\n\nThe initial MDL is a starting point. Improve it by:\n- Adding calculated columns based on business logic\n- Adding views for common query patterns\n- Refining descriptions based on actual query usage\n- Adding access control (RLAC/CLAC) if needed\n\nEach change follows: edit YAML → `wren context validate` →\n`wren context build` → `wren memory index`.\n\n## Quick reference\n\n| Task | Command / Method |\n|------|-----------------|\n| Discover tables | Agent's own tools (SQLAlchemy, driver, raw SQL) |\n| Discover columns + types | Agent's own tools |\n| Discover constraints | Agent's own tools |\n| Normalize types (Python) | `from wren.type_mapping import parse_type` |\n| Normalize types (CLI) | `wren utils parse-type --type T --dialect D` |\n| Normalize types (batch) | `wren utils parse-types --dialect D < columns.json` |\n| Scaffold project | `wren context init` |\n| Write models | Create `models/<name>/metadata.yml` |\n| Write relationships | Edit `relationships.yml` |\n| Validate | `wren context validate` |\n| Build manifest | `wren context build` |\n| Test query | `wren --sql \"SELECT * FROM <model> LIMIT 1\"` |\n| Index memory | `wren memory index` |\n\n## Things to avoid\n\n- Do not hardcode database-specific type strings in MDL — always normalize via `parse_type`\n- Do not skip validation before build — invalid YAML produces broken manifests silently\n- Do not guess column types — introspect from the actual database\n- Do not write relationships without confirming join conditions — wrong conditions cause silent query errors\n- Do not skip `wren memory index` after build — stale indexes degrade recall quality\n","frontmatter":{"name":"generate-mdl","description":"Generate a Wren MDL project by exploring a database with available tools (SQLAlchemy, database drivers, MCP connectors, or raw SQL). Guides agents through schema discovery, type normalization, and MDL YAML generation using the wren CLI. Use when: user wants to create or set up a new MDL, onboard a new data source, or scaffold a project from an existing database.","license":"Apache-2.0","metadata":{"author":"wrenai"}},"isInternal":false,"tokens":2976,"sizeBytes":12575},{"name":"SKILL.md","path":"core/wren/src/wren/skills_content/onboarding/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/core/wren/src/wren/skills_content/onboarding/SKILL.md","title":"onboarding","category":"anthropic-skill","format":"markdown","content":"---\nname: onboarding\ndescription: \"Onboard a user to Wren Engine end-to-end. Walks through environment checks, project scaffolding, connection configuration via .env, and first query. Use when: user wants to install Wren Engine, set up a new data source connection, or bootstrap a new project from scratch. Triggers: '/wren-onboarding', 'install wren', 'set up wren engine', 'wren onboarding', 'connect new database to wren'.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren Onboarding — Agent Workflow\n\nThis skill walks the agent through onboarding — environment checks, project scaffolding, profile creation, MDL generation, and first query. **Procedural details, per-datasource setup notes, and the troubleshooting playbook live in the docs**, not here. The skill's job is to enforce the agent-side rules (one step per turn, never ask for credentials in chat) and to dispatch the agent to the right doc / sibling skill at each step.\n\nReference docs (the skill points to these — never duplicate their content):\n- [`docs/core/get_started/installation.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/get_started/installation.md) — CLI install + skill install\n- [`docs/core/guides/connect.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md) — full connection procedure, **per-datasource setup notes, complete troubleshooting playbook**\n- [`docs/core/get_started/quickstart.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/get_started/quickstart.md) — bundled `jaffle_shop` demo\n\n## Mode of operation — READ THIS FIRST\n\n**One step per round-trip.** Each numbered step below is its own turn: explain briefly, ask **only** what the step needs, run the command(s), confirm, move on.\n\n- ❌ **Never collect information for future steps upfront.** Do not ask for project name + database type + credentials in one message.\n- ❌ **Never ask for credentials in chat — not host, port, user, password, tokens, anything.** Credentials always go through `.env`. The user fills the file in their editor; the agent never sees the values.\n- ❌ **Never query the database before MDL is built** via `wren skills get generate-mdl`.\n- ❌ **Never invent connection field names.** Always run `wren docs connection-info <ds>` to see the real fields — it's introspected from the live Pydantic schema, so it's always correct.\n- ✅ Wait for each command to finish, report its output in plain language, then move on.\n- ✅ For any error, consult `connect.md#troubleshooting` and surface the relevant section to the user — don't carry a copy of the playbook here.\n\n## Preflight (environment only — no user questions about the project)\n\nRead-only checks. Report findings, do **not** ask about project / credentials / datasource yet.\n\n1. `python3 --version` — requires Python 3.11+. If older, ask the user to upgrade and stop.\n2. Check virtualenv: `python3 -c \"import sys; print(sys.prefix != sys.base_prefix)\"`. If `False`, offer to create one (`python3 -m venv .venv && source .venv/bin/activate`). PEP 668 systems will need this.\n3. `wren --version` — if already installed, confirm before reinstalling.\n4. `pwd` — record it. Don't ask where the project should live yet.\n\nReport findings as a 4-bullet list, then continue.\n\n## Early branch — demo or own database?\n\n> \"Try the bundled `jaffle_shop` demo first (~30s, no DB needed), or connect your own database?\"\n\n- **demo** → point at [`quickstart.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/get_started/quickstart.md) and stop this skill.\n- **own DB** → continue.\n\n## Step 1 — Collect project name + database type\n\nThese two are the only thing Step 2 needs; ask both together so the user has a clean handoff:\n\n> \"Two things before I scaffold:\n> 1. **Project name** — I'll create `~/<name>/` and `cd` into it.\n> 2. **Database type** — run `wren docs connection-info` (no argument) to see the full list, or pick a common one: `postgres` (use for Aurora PostgreSQL), `mysql` (use for Aurora MySQL), `bigquery`, `snowflake`, `clickhouse`, `trino`, `duckdb`, …\"\n\nWait for both. Don't ask for credentials.\n\n## Step 2 — Workspace + .env setup (batch)\n\nSide effects: creates `~/<project>/`, installs `wrenai[<ds>,main]`, generates an empty `.env` template. The project files (`wren_project.yml` etc.) come later in Step 3.5 — at this point we only have a directory with credentials waiting to be filled.\n\nRun as a batch — report each command briefly, then end with one \"please fill `.env`\" ask:\n\n1. `mkdir -p ~/<project> && cd ~/<project>`.\n2. `pip install \"wrenai[<ds>,main]\"`. For datasource-specific install gotchas (macOS mysql, etc.), see [`connect.md#per-datasource-setup-notes`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md).\n3. **Generate the `.env` template by introspecting the connector**:\n\n   ```bash\n   wren docs connection-info <ds> --format md\n   ```\n\n   Use the field list to write `.env` with `<DS>_<FIELD>=` keys (UPPER_SNAKE), values **empty**. Example for postgres:\n\n   ```ini\n   POSTGRES_HOST=\n   POSTGRES_PORT=5432\n   POSTGRES_DATABASE=\n   POSTGRES_USER=\n   POSTGRES_PASSWORD=\n   ```\n\n   Special encodings (BigQuery base64, Snowflake account format, Athena AWS creds, etc.) are documented in [`connect.md#per-datasource-setup-notes`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md). Surface the relevant section to the user verbatim — don't paraphrase.\n\n4. Add `.env` to `.gitignore` if the project is a git repo. Suggest `chmod 600 .env`.\n5. Tell the user: `.env` is at `<path>`, please fill every value and reply **\"done\"**.\n\n## Step 3 — Create the connection profile\n\nOnly after the user replies \"done\".\n\nWrite `/tmp/conn.yml` with **every field as a `${VAR}` placeholder** matching the `.env` keys you generated in Step 2:\n\n```yaml\ndatasource: <ds>\nhost: ${<DS>_HOST}\nport: ${<DS>_PORT}\n# … one line per field from `wren docs connection-info <ds>`\n```\n\nThen:\n\n```bash\nwren profile add <project> --from-file /tmp/conn.yml\n```\n\nValidation runs automatically. The CLI overwrites profiles silently — there is no `--force` flag.\n\n- ✓ **Success** → continue to Step 3.5.\n- ⚠ **Any warning** → consult [`connect.md#troubleshooting`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md) for the exact symptom (missing secret, driver auth failure, ValidationError, unreachable host, …) and tell the user what to fix.\n\n## Step 3.5 — Scaffold the project\n\n```bash\nwren context init --empty\n```\n\nRefuses to overwrite an existing `wren_project.yml`. Creates the project directory layout (`models/`, `views/`, `relationships.yml`, `knowledge/` (rules + sql), `AGENTS.md`).\n\n## Step 3.6 — Bind the profile to the project\n\n```bash\nwren context set-profile <project>\n```\n\nWrites both `profile: <project>` and `data_source: <ds>` into `wren_project.yml` (data_source is taken from the profile we just validated, so it's guaranteed correct). Future CLI commands and the SDK resolve the connection deterministically — independent of which profile is globally active.\n\nThis step also future-proofs the project for multi-project setups: once the binding is recorded, switching `wren profile switch` elsewhere never breaks this project's queries.\n\n## Step 4 — Generate MDL (hand off)\n\n> ⚠️ The agent **must** build MDL before any data query. Queries against tables not in MDL will fail.\n\nRun `wren skills get generate-mdl` and follow it. It walks the agent through table introspection, type normalization, and YAML generation. When it finishes, return here and run:\n\n```bash\nwren context validate\nwren context build\n```\n\nReport the model count and any validate warnings.\n\n**Memory recommendation**: count models with `wren context show | grep -c '^model:'`. If `>= 200`, suggest `pip install \"wrenai[memory]\"` + `wren memory index` (~800 MB). If `< 200`, skip.\n\n## Step 5 — Ready to explore (hand off)\n\nSuggest 2–3 NL questions based on the discovered tables (e.g. for an orders schema: \"How many orders last month?\", \"Top 5 customers by total\"). Then end this skill: for day-to-day querying the agent should run `wren skills get usage`.\n\n## Cross-skill routing\n\n| Trigger | Skill |\n|---------|-------|\n| User mentions a SaaS source (HubSpot, Stripe, Salesforce, GitHub, Slack, …) | `wren skills get dlt-connector` |\n| User has a connected DB but no MDL yet | `wren skills get generate-mdl` |\n| User has MDL ready, wants to query | `wren skills get usage` |\n| Anything else from-scratch | `wren skills get onboarding` (this skill) |\n\n## On error\n\nDon't carry an error playbook here — surface [`connect.md#troubleshooting`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md) sections to the user. The doc covers:\n\n- `wren: command not found`\n- `pip install … externally-managed-environment`\n- Missing secret (`MissingSecretError`)\n- Driver authentication failures\n- Pydantic `ValidationError` / unknown datasource\n- Connection refused / firewall / cloud DB IP allow-list\n- `wren context validate` warning categories\n\nIf you hit something not in the playbook, tell the user:\n\n> \"I hit an error I don't know how to fix: `<error>`.\n> See <https://docs.getwren.ai/oss/introduction> or open an issue at <https://github.com/Canner/WrenAI/issues>.\"\n","frontmatter":{"name":"onboarding","description":"Onboard a user to Wren Engine end-to-end. Walks through environment checks, project scaffolding, connection configuration via .env, and first query. Use when: user wants to install Wren Engine, set up a new data source connection, or bootstrap a new project from scratch. Triggers: '/wren-onboarding', 'install wren', 'set up wren engine', 'wren onboarding', 'connect new database to wren'.","license":"Apache-2.0","metadata":{"author":"wrenai"}},"isInternal":false,"tokens":2340,"sizeBytes":9309},{"name":"SKILL.md","path":"core/wren/src/wren/skills_content/usage/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/core/wren/src/wren/skills_content/usage/SKILL.md","title":"usage","category":"anthropic-skill","format":"markdown","content":"---\nname: usage\ndescription: \"Wren Engine CLI workflow guide for AI agents. Answer data questions end-to-end using the wren CLI: gather schema context, recall past queries, write SQL through the MDL semantic layer, execute, and learn from confirmed results. Use when: user asks a data question, requests a report or analysis, asks about metrics, revenue, customers, orders, trends, or any business data; user says 'how many', 'show me', 'what is the', 'top N', 'compare', 'trend', 'growth', 'breakdown'; user wants to explore, analyze, filter, aggregate, or summarize data from a database; agent needs to query data, connect a data source, handle errors, or manage MDL changes via the wren CLI.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren Engine CLI — Agent Workflow Guide\n\n> This guide is served by the `wren` CLI (`wren skills get usage`), so it always matches your installed wrenai version. Pull the deeper reference docs with `wren skills get usage --full`.\n\n## Preflight — Verify environment and installation\n\n**Goal:** Ensure the `wren` CLI is available before entering any workflow.\n\n### Step 1 — Check Python virtual environment\n\nRun `python -c \"import sys; print(sys.prefix)\"` (or equivalent) to determine\nwhether a virtual environment is active.\n\n- If **no venv is active**, warn the user and ask whether to:\n  - Create one (e.g., `python -m venv .venv && source .venv/bin/activate`)\n  - Continue without a venv (not recommended — may pollute global packages)\n\n### Step 2 — Check if the `wren` CLI is installed\n\nRun `wren --version`. If the command is not found or errors:\n\n1. Tell the user that the `wren` CLI is not installed.\n2. Ask if you should help install it.\n3. If the user agrees, determine the **datasource extra** to install:\n\n   **Auto-detect from project:** Check whether the current directory is inside\n   a wren project (look for `wren_project.yml` up to the repository root).\n   If found, read the active profile with `cat ~/.wren/profiles.yml` or look\n   for a datasource hint in the project's profile configuration. Extract the\n   datasource type from there.\n\n   **Ask the user:** If no project is detected or no datasource can be\n   inferred, ask the user which database they plan to connect to. Valid\n   extras: `postgres` (for Aurora Postgres), `mysql` (for Aurora MySQL), `bigquery`, `snowflake`, `clickhouse`,\n   `trino`, `mssql`, `databricks`, `redshift`, `spark`, `athena`, `oracle`.\n   DuckDB is included by default — no extra needed.\n\n4. Install with the detected or chosen extra:\n   ```bash\n   # DuckDB (no extra needed)\n   pip install \"wrenai\"\n\n   # Other datasources\n   pip install \"wrenai[<datasource>]\"\n   ```\n   To also enable semantic memory, interactive prompts, and web UI (recommended):\n   ```bash\n   pip install \"wrenai[<datasource>,main]\"\n   # or for DuckDB:\n   pip install \"wrenai[main]\"\n   ```\n\n5. Verify: `wren --version`\n\nIf `wren --version` succeeds, proceed to the relevant workflow below.\n\n---\n\nThe `wren` CLI queries databases through an MDL (Model Definition Language) semantic layer. You write SQL against model names, not raw tables. The engine translates to the target dialect.\n\nTwo things drive everything:\n- **Profile** — database connection + datasource type, managed via `wren profile` (stored in `~/.wren/profiles.yml`)\n- **Project** — MDL model definitions in YAML, compiled to `target/mdl.json` via `wren context build`\n\nThe CLI reads the active profile for connection info and datasource. Use `wren profile list` to see which profile is active, `wren profile switch <name>` to change it. `dry-plan` also accepts `--datasource` / `-d` for transpile-only use without a profile.\n\nFor memory-specific decisions, see the `memory` reference (run `wren skills get usage --full`).\nFor SQL syntax, CTE-based modeling, and error diagnosis, see the `wren-sql` reference (run `wren skills get usage --full`).\nFor project structure, MDL field definitions, and CLI workflow details, see the [documentation](https://github.com/Canner/WrenAI/tree/main/docs/core).\n\n---\n\n## Workflow 1: Answering a data question\n\n### Step 1 — Gather context\n\n| Situation | Command |\n|-----------|---------|\n| Default | `wren memory fetch -q \"<question>\"` |\n| Need specific model's columns | `wren memory fetch -q \"...\" --model <name> --threshold 0` |\n| Memory not installed | Read `target/mdl.json` in the project directory, or run `wren context show` |\n\nIf this is the first query in the conversation, also run:\n\n```text\nwren context instructions\n```\n\nIf it returns content, treat it as **rules that override defaults** — apply them to all subsequent queries in this session.\n\n### Step 2 — Recall past queries\n\n```bash\nwren memory recall -q \"<question>\" --limit 3\n```\n\nUse results as few-shot examples. Skip if empty.\n\n### Step 2.5 — Assess complexity (before writing SQL)\n\nIf the question involves **any** of the following, consider decomposing:\n- Multiple metrics or aggregations (e.g., \"churn rate AND expansion revenue\")\n- Multi-step calculations (e.g., \"month-over-month growth rate\")\n- Comparisons across segments (e.g., \"by plan tier, by region\")\n- Time-series analysis requiring baseline + change (e.g., \"retention curve\")\n\n**Decomposition strategy:**\n1. Identify the sub-questions (e.g., \"total subscribers at start\" + \"subscribers who cancelled\" → churn rate)\n2. For each sub-question:\n   - `wren memory recall -q \"<sub-question>\"` — check if a similar pattern exists\n   - Write and execute a simple SQL\n   - Note the result\n3. Combine sub-results to answer the original question\n\n**When NOT to decompose:**\n- Single-table aggregation with GROUP BY — just write the SQL\n- Simple JOINs that the MDL relationships already define\n- Questions where `memory recall` returns a near-exact match\n\nThis is a judgment call, not a rigid rule. If you're confident in a single\nquery, go ahead. Decompose when the SQL would be hard to debug if it fails.\n\n### Step 3 — Write, verify, and execute SQL\n\n**For simple queries** (single table or simple MDL-defined JOINs, straightforward aggregation):\nExecute directly:\n```bash\nwren --sql 'SELECT c_name, SUM(o_totalprice) FROM orders\nJOIN customer ON orders.o_custkey = customer.c_custkey\nGROUP BY 1 ORDER BY 2 DESC LIMIT 5'\n```\n\n**For complex queries** (non-trivial JOINs not covered by MDL relationships, subqueries, multi-step logic):\nVerify first with dry-plan:\n```bash\nwren dry-plan --sql 'SELECT ...'\n```\n\nCheck the expanded SQL output:\n- Are the correct models and columns referenced?\n- Do the JOINs match expected relationships?\n- Are CTEs expanded correctly?\n\nIf the expanded SQL looks wrong, fix before executing.\nIf it looks correct, proceed:\n```bash\nwren --sql 'SELECT ...'\n```\n\n**SQL rules:**\n- Target MDL model names, not database tables\n- Write dialect-neutral SQL — the engine translates\n\n### Step 4 — Store and continue\n\nAfter successful execution, **store the query by default**:\n\n```bash\nwren memory store --nl \"<user's original question>\" --sql \"<the SQL>\"\n```\n\n**Skip storing only when:**\n- The query failed or returned an error\n- The user said the result is wrong\n- The query is exploratory (`SELECT * ... LIMIT N` without analytical clauses)\n- There is no natural language question — just raw SQL\n- The user explicitly asked not to store\n\nThe CLI auto-detects exploratory queries — if you see no store hint\nafter execution, the query was classified as exploratory.\n\n| Outcome | Action |\n|---------|--------|\n| User confirms correct | Store |\n| User continues with follow-up | Store, then handle follow-up |\n| User says nothing (but question had clear NL description) | Store |\n| User says wrong | Do NOT store — fix the SQL |\n| Query error | See Error recovery below |\n\n---\n\n## Workflow 2: Error recovery\n\n### \"table not found\"\n\n1. Verify model name: `wren memory fetch -q \"<name>\" --type model --threshold 0`\n2. Check MDL exists: `ls target/mdl.json` (or `wren context show`)\n3. Verify column: `wren memory fetch -q \"<column>\" --model <name> --threshold 0`\n\n### Connection error\n\n1. Check active profile: `wren profile debug`\n2. Verify datasource and connection fields are correct\n3. Test: `wren --sql \"SELECT 1\"`\n4. Valid datasource values: `postgres` (for Aurora Postgres), `mysql` (for Aurora MySQL), `bigquery`, `snowflake`, `clickhouse`, `trino`, `mssql`, `databricks`, `redshift`, `spark`, `athena`, `oracle`, `duckdb`\n5. If no profile exists, create one: `wren profile add --ui` (or `--interactive` / `--from-file`)\n\n### SQL syntax / planning error (enhanced)\n\n#### Layer 1: Identify the failure point\n\n```bash\nwren dry-plan --sql \"<failed SQL>\"\n```\n\n| dry-plan result | Failure layer | Next step |\n|-----------------|---------------|-----------|\n| dry-plan fails | MDL / semantic | → Layer 2A |\n| dry-plan succeeds, execution fails | DB / dialect | → Layer 2B |\n\n#### Layer 2A: MDL-level diagnosis (dry-plan failed)\n\nThe dry-plan error message tells you exactly what's wrong:\n\n| Error pattern | Diagnosis | Fix |\n|---------------|-----------|-----|\n| `column 'X' not found in model 'Y'` | Wrong column name | `wren memory fetch -q \"X\" --model Y --threshold 0` to find correct name |\n| `model 'X' not found` | Wrong model name | `wren memory fetch -q \"X\" --type model --threshold 0` |\n| `ambiguous column 'X'` | Column exists in multiple models | Qualify with model name: `ModelName.column` |\n| Planning error with JOIN | Relationship not defined in MDL | Check available relationships in context |\n\n**Key principle**: Fix ONE issue at a time. Re-run dry-plan after each fix\nto see if new errors surface.\n\n#### Layer 2B: DB-level diagnosis (dry-plan OK, execution failed)\n\nThe DB error + dry-plan output together pinpoint the issue:\n\n1. Read the dry-plan expanded SQL — this is what actually runs on the DB\n2. Compare with the DB error message:\n\n| Error pattern | Diagnosis | Fix |\n|---------------|-----------|-----|\n| Type mismatch | Column type differs from assumed | Check column type in context, add explicit CAST |\n| Function not supported | Dialect-specific function | Use dialect-neutral alternative |\n| Permission denied | Table/schema access | Check connection credentials |\n| Timeout | Query too expensive | Simplify: reduce JOINs, add filters, LIMIT |\n\n**For small models**: If the error message is unclear, try simplifying\nthe query to the smallest failing fragment. Execute subqueries independently\nto isolate which part fails.\n\nFor the CTE rewrite pipeline and additional error patterns, see the `wren-sql` reference (run `wren skills get usage --full`).\n\n---\n\n## Workflow 3: Connecting a new data source\n\n1. Add a profile: `wren profile add --ui` (or `--interactive` / `--from-file`)\n2. Test connection: `wren profile debug`\n3. Test query: `wren --sql \"SELECT 1\"`\n4. Initialize project: `wren context init`\n5. Build manifest: `wren context build`\n6. Index: `wren memory index`\n7. Verify: `wren --sql \"SELECT * FROM <model> LIMIT 5\"`\n\n---\n\n## Workflow 4: After MDL changes\n\nWhen model YAML files are updated, rebuild and re-index:\n\n```bash\n# 1. Validate changes\nwren context validate\n\n# 2. Rebuild manifest\nwren context build\n\n# 3. Re-index schema memory\nwren memory index\n\n# 4. Verify\nwren --sql \"SELECT * FROM <changed_model> LIMIT 1\"\n```\n\n---\n\n## Command decision tree\n\n```text\nGet data back           → wren --sql \"...\"\nAggregation across dims → wren cube query --cube <name> --measures <m> (if cube defined)\nRanked / top-N          → wren cube query ... --order-by \"<measure>:desc\" --limit N\nSee translated SQL only → wren dry-plan --sql \"...\" (accepts -d <datasource> if no active profile)\nValidate against DB     → wren dry-run --sql \"...\"\nSchema context          → wren memory fetch -q \"...\"\nFilter by type/model    → wren memory fetch -q \"...\" --type T --model M --threshold 0\nStore confirmed query   → wren memory store --nl \"...\" --sql \"...\"\nFew-shot examples       → wren memory recall -q \"...\"\nIndex stats             → wren memory status\nRe-index after MDL change → wren memory index\nShow project context    → wren context show\nRebuild manifest        → wren context build\nCheck profile           → wren profile debug\nSwitch profile          → wren profile switch <name>\n```\n\n---\n\n## Cube Query Workflow\n\nWhen the user asks an aggregation question (e.g., \"total revenue by month\",\n\"top customers\"), check if the MDL defines cubes before writing raw SQL.\n\n### Step 1: Discover cubes\n\n```bash\nwren cube list\n```\n\nIf cubes exist and cover the user's question, prefer cube query over raw SQL.\nLower error rate, especially for small / local models — agents don't have to\nhand-write GROUP BY / DATE_TRUNC.\n\n### Step 2: Inspect cube structure\n\n```bash\nwren cube describe <cube_name>\n```\n\nShows the cube's baseObject, measures (with expressions), dimensions,\ntime dimensions, and hierarchies.\n\n### Step 3: Match user's question to cube measures + dimensions\n\n| User phrase | Maps to |\n|---|---|\n| \"total revenue\" | `--measures total` |\n| \"by month\" | `--time-dimension \"order_date:month\"` |\n| \"in 2024\" | `--time-dimension \"order_date:month:2024-01-01,2025-01-01\"` |\n| \"for completed orders\" | `--filter \"status:eq:completed\"` |\n| \"top N customers\" | `--dimensions customer --order-by \"total:desc\" --limit N` |\n| \"worst / lowest / bottom N\" | `--order-by \"<measure>:asc\" --limit N` |\n| \"sorted by name\" | `--order-by \"<dimension>:asc\"` |\n\n### Step 4: Execute via CLI flags OR JSON input\n\nCLI flags:\n\n```bash\nwren cube query \\\n  --cube revenue \\\n  --measures total,order_count \\\n  --time-dimension \"order_date:month:2024-01-01,2025-01-01\" \\\n  --filter \"status:eq:completed\" \\\n  --order-by \"total:desc\" \\\n  --limit 100\n```\n\n**`--limit` without `--order-by` is not a top-N.** It returns an arbitrary N rows\n(or the earliest N by the time dimension when one is present). Whenever the user\nasks for \"top\", \"best\", \"worst\", or \"largest\", add `--order-by`.\n\nOrdering rules: the member must be one this query already selects (a measure,\ndimension, or time dimension); direction is lowercase `asc` or `desc`; each member\nmay appear once. Omitting `--order-by` keeps the previous default ordering.\n\nName the member as the cube declares it. A time dimension appears in the generated\nSQL as `<name>__<granularity>`, but `--order-by` wants the declared name:\n`--time-dimension \"order_date:month\" --order-by \"order_date:desc\"`.\n\nJSON input (good for agent-generated structured queries):\n\n```bash\necho '{\"cube\":\"revenue\",\"measures\":[\"total\"]}' | wren cube query --from -\n```\n\nAdd `--sql-only` to print the generated SQL without executing — useful for\nverification before paying for execution on a remote warehouse.\n\n### Step 5: Error recovery\n\n| Error | Action |\n|---|---|\n| `Unknown measure 'X'` | `wren cube describe <cube>` for available measures |\n| `Unknown dimension 'X'` | `wren cube describe <cube>` for available dimensions |\n| `Cube 'X' not found` | `wren cube list` |\n| `Cannot order by member 'X': member is not selected by the query` | Add `X` to `--measures` / `--dimensions`, or order by a member already selected |\n| `Cannot order by member 'X' more than once` | Drop the duplicate — each member may appear once in `--order-by` |\n| `` unknown variant `DESC`, expected `asc` or `desc` `` | Directions are lowercase: use `desc`, not `DESC` |\n| `Circular dependency detected` | Derived measure references itself — inspect the cube YAML |\n\n### When NOT to use cube query\n\nFall back to `wren --sql` when:\n\n- Custom JOINs across multiple models\n- Window functions, CTEs, or subqueries\n- Queries with no aggregation\n- No cubes defined in the MDL\n\n---\n\n## Aggregation decision tree\n\n```text\nUser question → Is it an aggregation question?\n                (SUM, COUNT, AVG, GROUP BY, \"by month\", \"per customer\", ...)\n  ├── Yes → Are cubes defined? (`wren cube list` once at start of session)\n  │         ├── Yes → Does a cube cover the question? (`wren cube describe`)\n  │         │         ├── Yes → Use `wren cube query` (preferred — lower error rate)\n  │         │         └── No  → Write raw SQL with `wren --sql`\n  │         └── No  → Write raw SQL with `wren --sql`\n  └── No  → Write raw SQL with `wren --sql` (look for memory recall first)\n```\n\n---\n\n## Things to avoid\n\n- Do not guess model or column names — check context first\n- Do not store failed queries or queries the user said are wrong\n- Do not skip storing successful queries with a clear NL question — default is to store\n- Do not re-index before every query — once per MDL change\n- Do not pass passwords via `--connection-info` if shell history is shared — use profiles (`wren profile add`) or `--connection-file`\n","frontmatter":{"name":"usage","description":"Wren Engine CLI workflow guide for AI agents. Answer data questions end-to-end using the wren CLI: gather schema context, recall past queries, write SQL through the MDL semantic layer, execute, and learn from confirmed results. Use when: user asks a data question, requests a report or analysis, asks about metrics, revenue, customers, orders, trends, or any business data; user says 'how many', 'show me', 'what is the', 'top N', 'compare', 'trend', 'growth', 'breakdown'; user wants to explore, analyze, filter, aggregate, or summarize data from a database; agent needs to query data, connect a data source, handle errors, or manage MDL changes via the wren CLI.","license":"Apache-2.0","metadata":{"author":"wrenai"}},"isInternal":false,"tokens":4194,"sizeBytes":16676},{"name":"README.md","path":"evals/spodbtify_ab/README.md","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/evals/spodbtify_ab/README.md","title":"Spodbtify_ab Skill","category":"anthropic-skill","format":"markdown","content":"# Spodbtify A/B Eval\n\nThis eval compares two workflows for turning the same DuckDB database into a\nqueryable Wren semantic layer, then scoring how well an AI coding agent answers\n20 analytical Spotify playlist questions.\n\nThe eval is intentionally agent-agnostic. It is not tied to Claude Code: use\nthe same spec with Claude, Codex, Cursor, an MCP client, or any runner that can\nconsume a prompt file and write an answer file.\n\n## Dataset\n\nExpected local dataset:\n\n```bash\nexport SPODBTIFY_DUCKDB_PATH=/path/to/spodbtify.duckdb\n```\n\nExpected dbt project artifacts:\n\n```bash\nexport SPODBTIFY_DBT_PROJECT_DIR=/path/to/spodbtify-dbt-project\n\n$SPODBTIFY_DBT_PROJECT_DIR/target/manifest.json\n$SPODBTIFY_DBT_PROJECT_DIR/target/catalog.json\n$SPODBTIFY_DBT_PROJECT_DIR/target/run_results.json\n$SPODBTIFY_DBT_PROJECT_DIR/target/compiled/\n```\n\nThe dataset is intentionally not checked into this repository.\n\n## Files\n\n- `spodbtify_ab_eval.json` is the canonical eval spec: workflows, controls,\n  table inventory, scoring rubric, questions, and result schema.\n- `agent_output.schema.json` is an optional JSON Schema for agents that support\n  structured output.\n- `run_eval.py` validates the spec, generates prompts, runs arbitrary agent\n  command templates, and summarizes score files.\n\n## Quick Validation\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py validate\n```\n\n## Generate Prompts\n\nPrint one prompt:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py prompt \\\n  --agent codex \\\n  --workflow dbt_integrated \\\n  --question 1\n```\n\nMaterialize prompt files for all questions without running an agent:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py run-agent \\\n  --agent codex \\\n  --workflow both \\\n  --dry-run\n```\n\n## Run With Any Agent CLI\n\n`run-agent` accepts a shell command template. These placeholders are available:\n\n- `{prompt_file}`\n- `{output_file}`\n- `{agent}`\n- `{workflow}`\n- `{question_id}`\n- `{schema_file}`\n\nExample shape:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py run-agent \\\n  --agent codex \\\n  --workflow schema_only \\\n  --command 'codex exec --sandbox danger-full-access --output-schema {schema_file} -o {output_file} - < {prompt_file}'\n```\n\nUse the equivalent command for Claude or another agent:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py run-agent \\\n  --agent claude \\\n  --workflow dbt_integrated \\\n  --command 'claude -p \"$(cat {prompt_file})\" > {output_file}'\n```\n\nExact CLI flags vary by tool and installation. The eval contract is the prompt\nfile in, answer file out. Keep one fresh session per workflow and do not share\nanswers or memory between workflows.\n\n## Scoring Contract\n\nAgents should return JSON with:\n\n```json\n{\n  \"question_id\": 1,\n  \"workflow\": \"dbt_integrated\",\n  \"agent\": \"codex\",\n  \"selected_tables\": [\"Top_Artists\", \"Artist_Collaborators\"],\n  \"sql\": \"SELECT ...\",\n  \"answer\": \"The analytical answer...\",\n  \"notes\": \"Optional caveats\"\n}\n```\n\nAfter a human or external grader assigns the three 0/1/2 scores for each\nquestion, store them in the score schema shown by:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py new-score-template \\\n  --agent codex \\\n  --workflow dbt_integrated \\\n  --output /tmp/codex-dbt-score.json\n```\n\nThen summarize:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py score --scores /tmp/codex-dbt-score.json\n```\n\nDo not commit agent answer files, run directories, or scored result files.\n","isInternal":false,"tokens":868,"sizeBytes":3353},{"name":"AUTHORING.md","path":"skills/AUTHORING.md","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/skills/AUTHORING.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# Skill Authoring Guide\n\n> **Where to author new skill content:**\n> **NOT here under `skills/`.** New skill guides ship as Python package data\n> in [`core/wren/src/wren/skills_content/<name>/`](../core/wren/src/wren/skills_content/),\n> served at runtime by `wren skills get <name>`. That keeps the content\n> version-aligned with the installed `wren` CLI — no skill cache, no\n> `versions.json` drift hack.\n>\n> This `skills/` tree only contains the **distribution stubs** that AI clients\n> install (one new discovery stub `wren/`, plus five deprecated redirect\n> stubs kept for one release).\n\nThe skill content format below still follows the\n[Agent Skills](https://agentskills.io/) open spec — the only thing that\nchanged is where the files live and how they reach the agent.\n\n---\n\n## Directory layout (under `core/wren/src/wren/skills_content/`)\n\n```text\n<name>/\n├── SKILL.md              # Required — frontmatter + workflow instructions\n├── references/           # Optional — reference docs, served by `wren skills get <name> --full`\n│   ├── some-topic.md\n│   └── another-topic.md\n└── scripts/              # Optional — bundled scripts, served by `wren skills get <name> --script <s>`\n```\n\n---\n\n## Frontmatter\n\n```yaml\n---\nname: <name>              # matches the parent directory; lowercase, hyphens\ndescription: \"What this skill does and when to trigger it. Include trigger\n  keywords so an AI client can match it to user intent.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n```\n\n**Drop the `version:` field.** Content version = the installed wrenai\nversion, since the SKILL.md ships inside the wheel.\n\n---\n\n## Progressive disclosure\n\n| Tier | Content | Loaded when |\n|------|---------|-------------|\n| 1 — Discovery stub | `skills/wren/SKILL.md` frontmatter description (~few hundred tokens) | At every AI-client session start |\n| 2 — Main guide | `wren skills get <name>` | The agent runs the command when the user's task matches |\n| 3 — References | `wren skills get <name> --full` | The agent opts in when it needs depth |\n| 3 — Scripts | `wren skills get <name> --script <s>` | Same |\n\nKeep `SKILL.md` under ~500 lines. Move reference-only content into `references/`.\n\n---\n\n## What goes where\n\n### Keep in `SKILL.md`\n- Step-by-step workflow the agent follows\n- Decision criteria and branching logic\n- Short commands the agent needs immediately\n- Quick reference tables (file paths, phase mappings, etc.)\n\n### Move to `references/`\n- Output templates (report formats, plan file formats)\n- Per-case investigation details\n- Large lookup tables\n- Anything that would push `SKILL.md` past ~500 lines\n\nCross-link inside the same skill by reference name (delivered via `--full`):\n```markdown\nFor the CTE rewrite pipeline, see the `wren-sql` reference (run\n`wren skills get usage --full`).\n```\n\nCross-link to another skill via its CLI command:\n```markdown\nFor day-to-day querying after setup, run `wren skills get usage`.\n```\n\nCross-link to a general doc:\n```markdown\nFor troubleshooting, see [`connect.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md).\n```\n\n---\n\n## Naming\n\n| Item | Convention | Example |\n|------|------------|---------|\n| Skill directory | `kebab-case` | `generate-mdl/` |\n| `name` field | same as directory | `generate-mdl` |\n| Reference files | descriptive `kebab-case` | `memory.md`, `wren-sql.md` |\n| Script files | descriptive `kebab-case` | `introspect_dlt.py` |\n\n---\n\n## Registering a new skill\n\n1. Create the directory under `core/wren/src/wren/skills_content/<name>/` with `SKILL.md` (and optional `references/`, `scripts/`).\n2. Author the content following the format above.\n3. Add a test to `core/wren/tests/unit/test_skills_cli.py` verifying `wren skills get <name>` returns the guide and `--full` inlines its references (if any).\n4. Done — there is no separate `versions.json` / `index.json` to update; the package-data ships with the wheel and `wren skills list` enumerates it automatically.\n\nNo `bash skills/check-versions.sh` step — version drift is impossible by\nconstruction (content travels with the binary).\n","isInternal":false,"tokens":1032,"sizeBytes":4135},{"name":"index.json","path":"skills/index.json","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/skills/index.json","title":"wrenai","category":"anthropic-skill","format":"json","content":"{\n  \"name\": \"wrenai\",\n  \"description\": \"Discovery stub for the Wren AI agent skill bundle. The actual workflow guides and prompt helpers live inside the wren CLI (`pip install wrenai`) and are served by `wren skills get` / `wren ask`.\",\n  \"homepage\": \"https://wren.ai\",\n  \"repository\": \"https://github.com/Canner/WrenAI\",\n  \"license\": \"Apache-2.0\",\n  \"skills\": [\n    {\n      \"name\": \"wren\",\n      \"description\": \"Wren CLI discovery stub. The actual workflow guides live inside the wren CLI; this stub teaches an AI agent to call `wren skills list`, `wren skills get <name>`, and `wren ask <prompt> --guided|--direct`. Use whenever the user asks a data question, wants to install / set up Wren Engine, connect a new database, load SaaS data via dlt, generate or regenerate an MDL project, enrich a project with business context, or build & deploy a shareable GenBI web app / dashboard from a project's semantic layer (Vercel / Cloudflare).\",\n      \"tags\": [\n        \"wren\",\n        \"discovery-stub\",\n        \"cli\",\n        \"sql\",\n        \"mdl\",\n        \"semantic-layer\",\n        \"genbi\"\n      ],\n      \"repository\": \"https://github.com/Canner/WrenAI/tree/main/skills/wren\"\n    }\n  ]\n}\n","frontmatter":{"name":"wrenai","description":"Discovery stub for the Wren AI agent skill bundle. The actual workflow guides and prompt helpers live inside the wren CLI (`pip install wrenai`) and are served by `wren skills get` / `wren ask`.","homepage":"https://wren.ai","repository":"https://github.com/Canner/WrenAI","license":"Apache-2.0","skills":[{"name":"wren","description":"Wren CLI discovery stub. The actual workflow guides live inside the wren CLI; this stub teaches an AI agent to call `wren skills list`, `wren skills get <name>`, and `wren ask <prompt> --guided|--direct`. Use whenever the user asks a data question, wants to install / set up Wren Engine, connect a new database, load SaaS data via dlt, generate or regenerate an MDL project, enrich a project with business context, or build & deploy a shareable GenBI web app / dashboard from a project's semantic layer (Vercel / Cloudflare).","tags":["wren","discovery-stub","cli","sql","mdl","semantic-layer","genbi"],"repository":"https://github.com/Canner/WrenAI/tree/main/skills/wren"}]},"isInternal":false,"tokens":315,"sizeBytes":1184},{"name":"README.md","path":"skills/README.md","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/skills/README.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"# Wren Engine — Agent Skill Distribution\n\nThe actual skill content (workflow guides, reference docs, prompt helpers)\n**lives inside the `wren` CLI**. This directory ships a single discovery stub\nthat an AI client installs once; the stub then tells the agent to fetch\neverything else from the CLI at runtime (so content always matches the\ninstalled wrenai version).\n\nSee [`SKILLS.md`](SKILLS.md) for the full design and command surface.\n\n## Install\n\n### The CLI itself (where all skill content lives)\n\n```bash\npip install wrenai\n```\n\n### The discovery stub (so an AI client knows the CLI exists)\n\n#### Option 1 — Claude Code plugin\n\n```text\n/plugin marketplace add Canner/WrenAI --path skills\n/plugin install wren@wren\n```\n\n#### Option 2 — `npx skills`\n\n```bash\nnpx skills add Canner/WrenAI\n```\n\nThe installer auto-detects your AI client. To target a specific one, add\n`--agent <name>` (e.g. `claude-code`, `cursor`, `windsurf`, `cline`).\n\n#### Option 3 — local install script\n\n```bash\nbash skills/install.sh                 # install the discovery stub\nbash skills/install.sh --force         # overwrite existing\n```\n\n## What the agent does with the stub\n\nOnce installed, the agent reads `wren/SKILL.md` and learns to call:\n\n```bash\nwren skills list                        # discover workflow guides\nwren skills get onboarding              # fetch a guide (one of 5 names)\nwren docs connection-info <ds>          # connection fields for a data source\nwren ask \"<question>\" --guided|--direct # wrap a prompt for an agent\n```\n\n## Requirements\n\n- `wren` CLI installed (`pip install wrenai` or `pip install \"wrenai[<extras>]\"`)\n- A database connection (configured via `wren profile add`)\n- An AI client that supports skills (Claude Code, Cursor, Cline, etc.)\n","isInternal":false,"tokens":435,"sizeBytes":1763},{"name":"SKILL.md","path":"skills/wren/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/skills/wren/SKILL.md","title":"wren","category":"anthropic-skill","format":"markdown","content":"---\nname: wren\ndescription: \"Wren CLI for AI agents — a semantic SQL layer over 22+ databases (Postgres, MySQL, BigQuery, Snowflake, Spark, …). The actual workflow guides live inside the `wren` CLI itself; this is just a discovery stub. Use whenever the user asks a data question (how many, show me, top N, compare, trend, breakdown, metric, revenue, customers, orders), wants to install / set up Wren Engine, connect a new database, connect SaaS data via dlt (HubSpot, Stripe, Salesforce, GitHub, Slack), generate or regenerate an MDL project from a database schema, enrich a project with business context (enum meanings, units, cubes like ARR / DAU / churn), or turn a project's context layer into a shareable GenBI web app / dashboard and deploy it to Vercel or Cloudflare. Triggers: 'install wren', 'set up wren engine', 'connect database to wren', 'connect SaaS to wren', 'load hubspot / stripe / salesforce data', 'generate mdl', 'scaffold wren project', 'enrich wren context', 'augment my project', 'add cubes', 'build a dashboard', 'make a shareable analytics app', 'deploy my context layer as a web app', 'genbi app', 'wren onboarding', 'wren usage', 'wren generate mdl', 'wren dlt connector', 'wren enrich context', 'wren genbi'.\"\nlicense: Apache-2.0\nallowed-tools: Bash(wren:*)\n---\n\n# Wren CLI\n\nThis is a discovery stub. The actual workflow guides and prompt helpers\nlive inside the `wren` CLI itself, so they always match the installed\nwrenai version (no skill cache, no version drift).\n\nInstall: `pip install wrenai`.\n\n## Workflow guides\n\n```bash\nwren skills list                        # all available workflow guides\nwren skills get onboarding              # set up Wren end-to-end\nwren skills get usage                   # day-to-day querying\nwren skills get generate-mdl            # generate MDL from a database schema\nwren skills get dlt-connector           # connect SaaS sources via dlt\nwren skills get enrich-context          # add business context (units, enums, cubes)\nwren skills get genbi                   # build & deploy a shareable GenBI web app\n# add --full to include the skill's reference docs\n# add --script <name> to fetch a bundled script (e.g. dlt-connector / introspect_dlt)\n```\n\n## Reference docs\n\nFull reference docs live on the web: <https://github.com/Canner/WrenAI/tree/main/docs/core>\n\n```bash\nwren docs connection-info <ds>          # required + optional connection fields for a data source\n```\n\n## Prompt enhancement (wraps a user question for an agent)\n\n```bash\nwren ask \"<question>\" --guided          # for weaker LLMs (strict task flow)\nwren ask \"<question>\" --direct          # for stronger LLMs (minimal wrapping)\n```\n\n## Day-to-day data commands (not a sub-app — top-level)\n\n```bash\nwren --sql '...'                        # execute SQL through the MDL layer\nwren query --sql '...'                  # same, explicit\nwren dry-plan --sql '...'               # transpile only, no DB hit\nwren context show / build / validate    # project / MDL lifecycle\nwren profile add / list / switch        # named connection profiles\nwren memory index / recall / store      # semantic memory (needs `[memory]` extra)\n```\n\nRun `wren --help` for the full surface; load the matching `wren skills get\n<name>` guide before driving any multi-step workflow.\n","frontmatter":{"name":"wren","description":"Wren CLI for AI agents — a semantic SQL layer over 22+ databases (Postgres, MySQL, BigQuery, Snowflake, Spark, …). The actual workflow guides live inside the `wren` CLI itself; this is just a discovery stub. Use whenever the user asks a data question (how many, show me, top N, compare, trend, breakdown, metric, revenue, customers, orders), wants to install / set up Wren Engine, connect a new database, connect SaaS data via dlt (HubSpot, Stripe, Salesforce, GitHub, Slack), generate or regenerate an MDL project from a database schema, enrich a project with business context (enum meanings, units, cubes like ARR / DAU / churn), or turn a project's context layer into a shareable GenBI web app / dashboard and deploy it to Vercel or Cloudflare. Triggers: 'install wren', 'set up wren engine', 'connect database to wren', 'connect SaaS to wren', 'load hubspot / stripe / salesforce data', 'generate mdl', 'scaffold wren project', 'enrich wren context', 'augment my project', 'add cubes', 'build a dashboard', 'make a shareable analytics app', 'deploy my context layer as a web app', 'genbi app', 'wren onboarding', 'wren usage', 'wren generate mdl', 'wren dlt connector', 'wren enrich context', 'wren genbi'.","license":"Apache-2.0","allowed-tools":"Bash(wren:*)","allowedTools":["Bash(wren:*)"]},"allowedTools":["Bash(wren:*)"],"isInternal":false,"tokens":814,"sizeBytes":3293},{"name":"marketplace.json","path":"skills/.claude-plugin/marketplace.json","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/skills/.claude-plugin/marketplace.json","title":"wren","category":"marketplace","format":"json","content":"{\n  \"name\": \"wren\",\n  \"owner\": {\n    \"name\": \"Canner\",\n    \"email\": \"dev@cannerdata.com\"\n  },\n  \"metadata\": {\n    \"description\": \"Wren Engine — discovery stub for AI agents. Actual workflow guides and prompt helpers ship inside the wren CLI (pip install wrenai) and are served by `wren skills get` / `wren ask`.\"\n  },\n  \"plugins\": [\n    {\n      \"name\": \"wren\",\n      \"source\": \".\",\n      \"description\": \"Wren CLI for AI agents — semantic SQL over 22+ databases. Installs a single discovery stub that points the agent at the wren CLI for all workflow content.\",\n      \"version\": \"1.0.0\",\n      \"keywords\": [\n        \"wren\",\n        \"sql\",\n        \"mdl\",\n        \"cli\",\n        \"semantic-layer\",\n        \"postgres\",\n        \"bigquery\",\n        \"snowflake\",\n        \"mysql\",\n        \"clickhouse\",\n        \"trino\",\n        \"mssql\",\n        \"databricks\",\n        \"redshift\",\n        \"spark\",\n        \"athena\",\n        \"oracle\",\n        \"duckdb\"\n      ]\n    }\n  ]\n}\n","frontmatter":{"name":"wren","owner":{"name":"Canner","email":"dev@cannerdata.com"},"metadata":{"description":"Wren Engine — discovery stub for AI agents. Actual workflow guides and prompt helpers ship inside the wren CLI (pip install wrenai) and are served by `wren skills get` / `wren ask`."},"plugins":[{"name":"wren","source":".","description":"Wren CLI for AI agents — semantic SQL over 22+ databases. Installs a single discovery stub that points the agent at the wren CLI for all workflow content.","version":"1.0.0","keywords":["wren","sql","mdl","cli","semantic-layer","postgres","bigquery","snowflake","mysql","clickhouse","trino","mssql","databricks","redshift","spark","athena","oracle","duckdb"]}]},"isInternal":false,"tokens":261,"sizeBytes":964},{"name":"plugin.json","path":"skills/.claude-plugin/plugin.json","rawUrl":"https://raw.githubusercontent.com/Canner/WrenAI/HEAD/skills/.claude-plugin/plugin.json","title":"wren","category":"plugin-manifest","format":"json","content":"{\n  \"name\": \"wren\",\n  \"description\": \"AI agent skills for Wren Engine CLI — semantic SQL layer for 22+ data sources.\",\n  \"version\": \"1.0.0\",\n  \"author\": {\n    \"name\": \"Canner\",\n    \"url\": \"https://www.getwren.ai/\"\n  },\n  \"homepage\": \"https://www.getwren.ai/\",\n  \"repository\": \"https://github.com/Canner/WrenAI\",\n  \"license\": \"Apache-2.0\",\n  \"keywords\": [\n    \"wren\",\n    \"sql\",\n    \"mdl\",\n    \"cli\",\n    \"semantic-layer\",\n    \"data-source\",\n    \"postgres\",\n    \"bigquery\",\n    \"snowflake\",\n    \"mysql\",\n    \"clickhouse\",\n    \"trino\",\n    \"mssql\",\n    \"databricks\",\n    \"redshift\",\n    \"spark\",\n    \"athena\",\n    \"oracle\",\n    \"duckdb\"\n  ]\n}\n","frontmatter":{"name":"wren","description":"AI agent skills for Wren Engine CLI — semantic SQL layer for 22+ data sources.","version":"1.0.0","author":{"name":"Canner","url":"https://www.getwren.ai/"},"homepage":"https://www.getwren.ai/","repository":"https://github.com/Canner/WrenAI","license":"Apache-2.0","keywords":["wren","sql","mdl","cli","semantic-layer","data-source","postgres","bigquery","snowflake","mysql","clickhouse","trino","mssql","databricks","redshift","spark","athena","oracle","duckdb"]},"isInternal":false,"tokens":208,"sizeBytes":643}],"systemPromptSnippet":"<yakaai_skills repo=\"Canner/WrenAI\">\n<!-- File: AGENTS.md (Tokens: ~124 | Category: root-instruction) -->\n# AGENTS.md\n\nRepository-wide instructions live in `.claude/CLAUDE.md`. Please refer to that file for shared build commands, architecture, and conventions.\n\n**Before opening a pull request, read the \"Contribution Bar\" section of that file.** It sets out what is expected of a change here — reproduce a failure before claiming one, label honestly, make tests exercise the code they cover — and what will be closed rather than reviewed.\n\nPer-module instructions live in `core/<module>/.claude/CLAUDE.md`, each with its own `AGENTS.md` pointing at it.\n\n<!-- File: skills/SKILLS.md (Tokens: ~364 | Category: root-instruction) -->\n# Wren Engine — Agent Skills\n\nThe actual workflow guides, reference docs, and prompt helpers live **inside\nthe `wren` CLI itself**, so they always match the installed wrenai\nversion (no skill cache, no version drift).\n\nThis directory ships a single discovery stub ([`wren/SKILL.md`](wren/SKILL.md))\nthat an AI client can install. Once the agent reads the stub, it learns to\nfetch everything else from the CLI on demand:\n\n```bash\nwren skills list                        # all available workflow guides\nwren skills get <name>                  # fetch a guide\nwren skills get <name> --full           # include the guide's reference docs\nwren skills get <name> --script <s>     # fetch a bundled script\n\nwren docs connection-info <ds>          # connection fields for a data source\n\nwren ask \"<question>\" --guided          # wrap a question for a weaker LLM\nwren ask \"<question>\" --direct          # wrap a question for a stronger LLM\n```\n\n## Install\n\n```bash\npip install wrenai                 # the CLI (everything is here)\nnpx skills add Canner/WrenAI            # install the discovery stub for AI clients\n```\n\nOr via Claude Code's plugin marketplace:\n\n```text\n/plugin marketplace add Canner/WrenAI --path skills\n/plugin install wren@wren\n```\n\n## Writing a new skill\n\nNew skill guides ship as Python package data in\n[`core/wren/src/wren/skills_content/<name>/`](../core/wren/src/wren/skills_content/),\nnot as a new directory under this `skills/` tree. See\n[`AUTHORING.md`](AUTHORING.md).\n\n<!-- File: .claude/CLAUDE.md (Tokens: ~2141 | Category: claude-rule) -->\n# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nWren is an open-source semantic engine for MCP clients and AI agents. It translates SQL queries through a semantic layer (MDL — Modeling Definition Language) and executes them against 22+ data sources (PostgreSQL, BigQuery, Snowflake, Spark, etc.). The Rust engine is powered by Apache DataFusion (upstream, crates.io v55).\n\nThe previous WrenAI services (`wren-ui/`, `wren-ai-service/`, `wren-launcher/`, `docker/`, `deployment/`) were moved to the `legacy/v1` branch (tag `v1-final`) as of the wren-engine import. Active development is focused on the Open Context Engine.\n\n## Repository Structure\n\n```\ncore/\n├── wren-core/        Rust semantic engine (Cargo workspace; crates.io: wren-semantic-core, lib name wren_core)\n├── wren-core-base/   Shared Rust crate — manifest types (Model, Column, Metric, Relationship, View) + ManifestBuilder (crates.io: wren-core-base)\n├── wren-core-py/     PyO3 bindings exposing wren-core to Python (PyPI: wren-core)\n├── wren-core-wasm/   WebAssembly build of wren-core for in-browser semantic SQL (npm: wren-core-wasm)\n├── wren/             Python SDK and CLI — `wren` command, profile/context/memory management (PyPI: wrenai)\n└── wren-mdl/         MDL JSON schema definition\n\ndocs/core/            Module documentation\nexamples/             Example projects (placeholder — to be populated)\nskills/               CLI-based agent skills (wren-generate-mdl, wren-usage, wren-dlt-connector, wren-onboarding)\nscripts/              Repo helper scripts\n```\n\n## Build & Development Commands\n\n### core/wren-core (Rust)\n```bash\ncd core/wren-core\ncargo check --all-targets                                  # compile check\ncargo test --lib --tests --bins                            # tests (set RUST_MIN_STACK=8388608)\ncargo fmt --all                                            # format\ncargo clippy --all-targets --all-features -- -D warnings   # lint\ntaplo fmt                                                  # format Cargo.toml files\n```\n\nMost unit tests live in `core/wren-core/core/src/mdl/mod.rs`. SQL end-to-end tests use sqllogictest files in `core/wren-core/sqllogictest/test_files/`.\n\n### core/wren-core-py (Python bindings)\n```bash\ncd core/wren-core-py\njust install      # uv sync (deps only; --no-install-project)\njust develop      # build dev wheel with maturin\njust test-rs      # Rust tests (cargo test --no-default-features)\njust test-py      # Python tests (pytest)\njust test         # both\njust format       # cargo fmt + ruff + taplo\n```\n\n### core/wren-core-wasm (WASM)\n```bash\ncd core/wren-core-wasm\njust build        # wasm-pack build (browser target)\njust test         # wasm-pack test\n```\nOutputs a ~68 MB WASM binary; distributed via npm and unpkg (jsDelivr's 50 MB per-file CDN limit blocks it).\n\n### core/wren (SDK & CLI)\n```bash\ncd core/wren\njust install              # uv sync (locked prebuilt wren-core-py wheel from PyPI; no Rust build)\njust install-all          # with all optional extras (incl. memory)\njust install-extra <e>    # e.g. just install-extra postgres\njust install-memory       # memory extra (lancedb + sentence-transformers)\njust install-local        # engine dev: uv sync + build local wheel + overlay\njust use-local-core       # rebuild + re-overlay after Rust changes\njust dev                  # run `wren` CLI\njust test                 # pytest tests/\njust test-memory          # memory-specific tests\njust lint                 # ruff format --check + ruff check\njust format               # ruff auto-fix\njust build                # uv build (produces wheel)\n```\n\nUses `uv` (not Poetry). `pyproject.toml` uses `hatchling` as build backend. Optional extras: `postgres`, `mysql`, `bigquery`, `snowflake`, `clickhouse`, `trino`, `mssql`, `databricks`, `redshift`, `spark`, `athena`, `oracle`, `memory`, `all`, `dev`.\n\n## Architecture: Query Flow\n\n```\nSQL query\n  → wren CLI / wren-core-py\n  → wren-core (Rust): MDL analysis → logical plan → optimization\n  → DataFusion (query planning, upstream crates.io v55)\n  → connector-specific SQL (Ibis / sqlglot)\n  → native execution on the target data source\n```\n\n## Key Architecture Details\n\n**wren-core internals** (`core/wren-core/core/src/`):\n- `mdl/` — Core MDL processing: `WrenMDL` (manifest + symbol table), `AnalyzedWrenMDL` (with lineage), function definitions per dialect (scalar/aggregate/window), type planning\n- `logical_plan/analyze/` — DataFusion analyzer rules: `ModelAnalyzeRule` (TableScan → ModelPlanNode), scope tracking, access control (RLAC/CLAC), view expansion, relationship chain resolution\n- `logical_plan/optimize/` — Optimization passes: type coercion, timestamp simplification\n- `sql/` — SQL parsing and analysis\n\n**Manifest types** (`core/wren-core-base/src/mdl/`):\n- `manifest.rs` — `Manifest`, `Model`, `Column`, `Metric`, `Relationship`, `View`, `RowLevelAccessControl`, `ColumnLevelAccessControl`\n- `builder.rs` — Fluent `ManifestBuilder` API\n- Uses `wren-manifest-macro` for auto-generating Pydantic-compatible Python classes\n\n## Known wren-core Limitations\n\n**ModelAnalyzeRule — correlated subquery column resolution**: cannot resolve outer column references inside correlated subqueries; only sees the subquery's own table scope. Affects TPCH Q2, Q4, Q15, Q17, Q20, Q21, Q22.\n\n## Conventions\n\n- **Commits**: Conventional commits (`feat:`, `fix:`, `chore:`, `refactor:`, `test:`, `docs:`, `perf:`, `deps:`). Releases are automated via release-please with independent release lines per module.\n- **Rust**: format with `cargo fmt`, lint with `clippy -D warnings`, TOML with `taplo`.\n- **Python**: format and lint with `ruff` (line-length 88, target Python 3.11). Both `core/wren-core-py` and `core/wren` use uv.\n- **DataFusion**: upstream `datafusion` v55 from crates.io (no longer the Canner fork).\n- **Snapshot testing**: wren-core uses `insta` for Rust snapshot tests.\n- **CI**: Per-module path-filtered workflows trigger only on changes inside that module.\n\n## Contribution Bar\n\nRead this before opening a pull request. These rules exist because a large share of\nincoming PRs fail them; a PR that violates one will be closed rather than iterated on.\n\n### Prove the problem before fixing it\n\n- **Do not assert the behaviour of an external system you have not observed.** If you\n  claim an engine, driver, or library rejects some input, reproduce it and paste the\n  actual error into the description. Unevidenced claims — \"the parser rejects a trailing\n  semicolon\", \"some clients reject this\" — are rejected on sight; several such claims\n  have turned out to be false when tested.\n- **Do not add a guard for a state you have not shown to be reachable.** Trace the call\n  path and say in the description how the bad value gets there. Where the repo already\n  validates (see \"Validation boundaries\" in `core/wren/.claude/CLAUDE.md`), re-validating\n  downstream is dead code.\n- If you cannot reproduce a failure, you have a hypothesis, not a bug. **Open an issue\n  with the reproduction attempt, not a PR.**\n\n### Label honestly\n\n- `fix:` requires a reproducible failure that the change repairs. A change with no\n  observable difference in behaviour is `refactor:` or `chore:`.\n- The description must say what changes for a user. \"Improves robustness\" and\n  \"hardens X\" are not answers.\n\n### Tests must exercise the code you changed\n\n- A test that reads a source file as text and asserts on substrings tests nothing. Call\n  the function; assert on what it returns, or on what it passes downstream.\n- Every assertion must be capable of failing. An assertion about a condition the code\n  cannot produce is noise.\n- Confirm the test actually runs in CI. Some test files are gated behind optional extras\n  or containers and are skipped by the default jobs — a service-free test placed in one\n  of those files never executes.\n\n### One change, once\n\n- Search open PRs before opening a new one. Duplicate PRs against the same file are\n  closed without review.\n- A mechanical change repeated across several files or connectors belongs in **one** PR,\n  not one PR per file. Reviewers need to see the resulting convention in a single diff.\n\n### Respect decisions already recorded\n\n- If a comment or a test documents a deliberate choice, do not silently reverse it.\n  Quote it in the description and argue why it should change.\n\n### Keep the branch current\n\n- Rebase onto the target branch before requesting review. A branch whose last CI run was\n  against an old base can merge cleanly and still be broken — a textually clean merge is\n  not a semantically correct one.\n\n<!-- File: core/wren/src/wren/skills_content/dlt-connector/SKILL.md (Tokens: ~2825 | Category: anthropic-skill) -->\n---\nname: dlt-connector\ndescription: \"Connect SaaS data (HubSpot, Stripe, Salesforce, GitHub, Slack, etc.) to Wren Engine for SQL analysis. Guides the user through the full flow: install dlt, pick a SaaS source, set up credentials, run the data pipeline into DuckDB, then auto-generate a Wren semantic project from the loaded data. Use this skill whenever the user mentions: connecting SaaS data, importing data from an API, dlt pipelines, loading HubSpot/Stripe/Salesforce/GitHub/Slack data, querying SaaS data with SQL, or setting up a new data source from a REST API. Also trigger when the user already has a dlt-produced DuckDB file and wants to create a Wren project from it.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# wren-dlt-connector\n\n> Reference docs (`dlt_sources`) and the `introspect_dlt` script are bundled. Pull references with `wren skills get dlt-connector --full`; fetch a script with `wren skills get dlt-connector --script <name>`.\n\nConnect SaaS data to Wren Engine for SQL analysis — from zero to a verified, queryable project in one conversation.\n\n## Who this is for\n\nData analysts who know SQL and some Python, but may not have used dlt or Wren before. Explain concepts briefly when they first appear, but don't over-explain things a SQL-literate person would already know.\n\n## Overview\n\nThis skill walks through a four-phase workflow:\n\n1. **Extract** — Use dlt (data load tool) to pull data from a SaaS API into a local DuckDB file\n2. **Model** — Introspect the DuckDB schema and auto-generate a Wren semantic project (YAML models, relationships, profile)\n3. **Build & Verify** — Build the project and run actual SQL queries to confirm everything works end-to-end\n4. **Handoff** — Show the user their data and next steps\n\nThe user might enter at any phase. Ask which phase they're starting from — they may already have a `.duckdb` file and just need phases 2–4.\n\n**The goal is a project that actually queries successfully, not just files that look correct.** Always run the verification step before declaring success.\n\n## Critical: DuckDB catalog naming\n\nWhen wren engine connects to a DuckDB file, it ATTACHes it using the filename (without `.duckdb` extension) as the catalog alias:\n\n```\nATTACH DATABASE 'stripe_data.duckdb' AS \"stripe_data\" (READ_ONLY)\n```\n\nThis means **every model's `table_reference.catalog` must equal the DuckDB filename stem**. If the file is `hubspot.duckdb`, the catalog is `hubspot`. If it's `my_pipeline.duckdb`, the catalog is `my_pipeline`.\n\nGetting this wrong causes \"table not found\" errors at query time. The `introspect_dlt.py` script handles this automatically.\n\n## Critical: Type normalization\n\nColumn types must be normalized using wren SDK's `type_mapping.parse_type()` function, which uses sqlglot to convert database-specific types (like DuckDB's `HUGEINT`, `TIMESTAMP WITH TIME ZONE`) into canonical SQL types that wren-core understands. Do not hardcode type mappings — always delegate to `parse_type(raw_type, \"duckdb\")`.\n\nThe `introspect_dlt.py` script does this automatically when wren SDK is installed.\n\n## Phase 1: Extract — dlt Pipeline Setup\n\n### Step 1: Pick the SaaS source\n\nAsk the user which SaaS service they want to connect. Read `dlt_sources` for a list of popular verified sources and their auth requirements. If the source isn't listed, check whether dlt has a verified source for it by searching `dlthub.com/docs/dlt-ecosystem/verified-sources`.\n\n### Step 2: Install dlt\n\n```bash\npip install \"dlt[duckdb]\" --break-system-packages\n```\n\n### Step 3: Write the pipeline script\n\nCreate a Python script that:\n1. Imports the dlt source function for the chosen SaaS\n2. Configures the pipeline with `destination='duckdb'` and a local file path\n3. Runs the pipeline with `pipeline.run(source)`\n\nHere's the general pattern — adapt it per source (check `dlt_sources` for source-specific templates):\n\n```python\nimport dlt\n\npipeline = dlt.pipeline(\n    pipeline_name=\"<source>_pipeline\",\n    destination=\"duckdb\",\n    dataset_name=\"<source>_data\",\n)\n\n# Source-specific: check the dlt_sources reference for auth patterns\nsource = <source_function>(api_key=dlt.secrets.value)\n\ninfo = pipeline.run(source)\nprint(info)\n```\n\n### Step 4: Set up credentials\n\ndlt reads credentials from environment variables or `.dlt/secrets.toml`. The simplest approach for a one-time run:\n\n```bash\n# Set the credential as an environment variable\n# The exact variable name depends on the source — check the dlt_sources reference\nexport SOURCES__<SOURCE>__API_KEY=\"the-actual-key\"\n```\n\nAsk the user for their API key or token. Remind them:\n- Never commit credentials to git\n- Environment variables are the simplest way for a one-time run\n- For repeated use, they can create `.dlt/secrets.toml`\n\n### Step 5: Run the pipeline\n\n```bash\npython <pipeline_script>.py\n```\n\nAfter the run, confirm:\n1. The pipeline completed without errors\n2. A `.duckdb` file was created (usually at `<pipeline_name>.duckdb`)\n3. Print discovered tables and their column counts\n\n```python\nimport duckdb\n\ncon = duckdb.connect(\"<pipeline_name>.duckdb\", read_only=True)\nfor row in con.execute(\"\"\"\n    SELECT table_schema, table_name,\n           (SELECT COUNT(*) FROM information_schema.columns c\n            WHERE c.table_schema = t.table_schema AND c.table_name = t.table_name) as col_count\n    FROM information_schema.tables t\n    WHERE table_schema NOT IN ('information_schema', 'pg_catalog')\n      AND table_name NOT LIKE '_dlt_%'\n    ORDER BY table_schema, table_name\n\"\"\").fetchall():\n    print(f\"  {row[0]}.{row[1]} ({row[2]} columns)\")\ncon.close()\n```\n\n## Phase 2: Model — Generate Wren Project\n\nRun the introspection script to auto-generate a complete Wren project from the DuckDB file:\n\n```bash\n# first fetch the script: wren skills get dlt-connector --script introspect_dlt > introspect_dlt.py\npython introspect_dlt.py \\\n    --duckdb-path <path-to-duckdb-file> \\\n    --output-dir <project-directory> \\\n    --project-name <name>\n```\n\nThis script:\n- Connects to the DuckDB file (read-only)\n- **Sets `table_reference.catalog` to the DuckDB filename stem** (matching wren engine's ATTACH behavior)\n- Discovers all tables and columns via `information_schema`\n- Filters out dlt internal tables (`_dlt_loads`, `_dlt_pipeline_state`, etc.)\n- Filters out dlt metadata columns (`_dlt_id`, `_dlt_load_id`, `_dlt_list_idx`) from model definitions\n- Detects parent-child relationships from `_dlt_parent_id` columns and table naming conventions\n- **Normalizes column types using `wren.type_mapping.parse_type()`** (sqlglot-based)\n- Generates a complete v5 YAML project (wren_project.yml, models/, relationships.yml, knowledge/rules/)\n\nAfter running, show the user what was generated:\n\n```bash\n# Show project summary\ncat <project-directory>/wren_project.yml\necho \"---\"\nls <project-directory>/models/\necho \"---\"\ncat <project-directory>/relationships.yml\n```\n\n### Verify model correctness\n\nSpot-check one generated model to confirm:\n1. `table_reference.catalog` matches the DuckDB filename (e.g., `stripe_data` for `stripe_data.duckdb`)\n2. `table_reference.schema` matches the DuckDB schema (usually `main`)\n3. No `_dlt_*` columns appear in the columns list\n4. Column types look reasonable (VARCHAR, BIGINT, BOOLEAN, TIMESTAMP, etc.)\n\n### Set up the connection profile\n\nCreate a Wren profile so the user can query without specifying connection details every time. The `url` must point to the **directory containing** the `.duckdb` file (not the file itself):\n\n```python\nimport yaml\nfrom pathlib import Path\n\nwren_home = Path.home() / \".wren\"\nwren_home.mkdir(exist_ok=True)\nprofiles_file = wren_home / \"profiles.yml\"\n\nexisting = (\n    (yaml.safe_load(profiles_file.read_text()) or {}) if profiles_file.exists() else {}\n)\nexisting.setdefault(\"profiles\", {})\n\nprofile_name = \"<source>_dlt\"\nexisting[\"profiles\"][profile_name] = {\n    \"datasource\": \"duckdb\",\n    \"url\": str(Path(\"<duckdb-path>\").resolve().parent),\n    \"format\": \"duckdb\",\n}\nexisting[\"active\"] = profile_name\n\nprofiles_file.write_text(yaml.dump(existing, default_flow_style=False, sort_keys=False))\n```\n\n## Phase 3: Build & Verify — The Project Must Actually Work\n\nThis phase is not optional. A project that generates YAML but fails at query time is not a success.\n\n### Step 1: Build the MDL\n\n```bash\ncd <project-directory>\nwren context build\n```\n\nThis compiles the YAML models into `target/mdl.json`. If this fails, fix the issues before proceeding (see Troubleshooting below).\n\n### Step 2: Validate with a real query\n\nRun at least one query per generated model to confirm the project is functional:\n\n```bash\n# For each model, verify it resolves correctly\nwren --sql 'SELECT COUNT(*) as total FROM \"<table_name>\"'\n```\n\nIf any query fails, debug and fix the model before moving on. Common issues:\n- Wrong catalog in table_reference → \"table not found\"\n- Type mismatch → fix the column type in metadata.yml\n- Missing profile → check `wren profile list`\n\n### Step 3: Run interesting queries\n\nOnce basic queries pass, run 2–3 more interesting queries to show the user what their data looks like:\n\n```bash\n# Preview data\nwren --sql 'SELECT * FROM \"<table_name>\" LIMIT 5'\n\n# If there's a relationship, verify both models are queryable\nwren --sql 'SELECT * FROM \"<parent>\" LIMIT 5'\nwren --sql 'SELECT * FROM \"<child>\" LIMIT 5'\n```\n\nShow the results to the user and explain what they're seeing. This is their first look at the data through Wren — make it count.\n\n### Step 4: Confirm success\n\nOnly after queries return real data, tell the user the setup is complete. Summarize:\n- How many models were created\n- What relationships were detected\n- Which profile is active\n- Example queries they can try next\n\n### Next step: share it as an app\n\nThe project is now DuckDB-backed, which is exactly what GenBI snapshot mode\nwants. If the user wants to turn this data into a shareable dashboard / web app\nand deploy it (Vercel / Cloudflare), hand off to the GenBI workflow:\n`wren skills get genbi`. Its snapshot source is the very `.duckdb` file this\npipeline produced.\n\n## Troubleshooting\n\nIf `wren context build` fails:\n- Check that `data_source: duckdb` is set in `wren_project.yml`\n- Verify the DuckDB file path in the profile is correct\n- Run `wren context validate` for detailed error messages\n\nIf queries fail with \"table not found\":\n- **Most likely cause:** `table_reference.catalog` doesn't match the DuckDB filename. If the file is `pipeline.duckdb`, the catalog must be `pipeline`, not empty string.\n- Check the profile's `url` points to the directory containing the `.duckdb` file\n- Table names with double underscores need quoting: `\"hubspot__contacts\"`\n\nIf queries fail with type errors:\n- Check column types in the model YAML — they should be canonical SQL types (VARCHAR, BIGINT, etc.)\n- Re-run `introspect_dlt.py` with wren SDK installed to get proper type normalization\n\nGeneral:\n- Check that the profile is active: `wren profile list`\n- The DuckDB file might be locked if a dlt pipeline is running — wait for it to finish\n\n## Important notes\n\n- dlt's `_dlt_parent_id` / `_dlt_id` columns are kept in the actual DuckDB tables but hidden from Wren model definitions. They're only used in relationship conditions.\n- DuckDB has a single-writer limitation. Don't run a dlt sync while querying. For concurrent access, dlt should write to a separate file and swap atomically.\n- The generated models use `table_reference` (not `ref_sql`) since they map directly to DuckDB tables created by dlt.\n- Column types are normalized using wren SDK's `parse_type()` with sqlglot's DuckDB dialect. If a type looks wrong, the user can edit the model's `metadata.yml` directly.\n\n<!-- File: core/wren/src/wren/skills_content/enrich-context/SKILL.md (Tokens: ~6599 | Category: anthropic-skill) -->\n---\nname: enrich-context\ndescription: \"Augment a Wren project with business context that DB schema cannot carry — enum value meanings, units (USD vs cents, ms vs sec), NULL semantics, magic sentinels (-1 = unknown), soft-delete default filters, business synonyms, time-grain / TZ conventions, cross-system identifiers, currency rules, canonical-table preferences, AND named aggregation metrics (ARR, churn, DAU, WAU, NRR) proposed as cubes. Runs in one of two modes selected at session start: `grill` (one question at a time, user-driven) or `auto-pilot` (agent infers and applies, escalates only on conflicts and high-blast-radius additions like new cubes / views / relationships). Reads everything under <project>/raw/ (PDFs, glossaries, handbooks, code, data dictionaries) and optionally samples low-cardinality columns from the live DB (grill mode), compares against the current MDL / cubes / knowledge (rules + NL→SQL pairs), then fills gaps via the ten-category gap catalog and the cube proposal flow. Confirmed findings are written back to the right sink. Use when: user says 'enrich context', 'augment my project', 'grill me on this project', 'auto-fill my context', 'agent doesn't understand our docs / enum values / units / null meanings', 'business context is missing', 'what does status=A mean', 'is this amount in USD or cents', 'we keep getting wrong aggregations', 'add cubes for ARR / DAU / churn', 'we have a handbook / glossary / data dictionary the agent should know'; or after generating an MDL and noticing the agent lacks business semantics.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren Enrich Context — Fill the Business-Context Gap\n\nThis skill exists because most business context never lives in a DB schema — it lives in handbooks, glossaries, finance reports, support playbooks, code comments, Slack rules-of-thumb. The agent reads those raw artifacts, finds what's missing from the Wren project, and **either grills the user one question at a time (grill mode) or applies its best inferences directly and hands over an audit (auto-pilot mode)** before writing back. The output lands in the sinks each project already has — MDL, `cubes/`, `knowledge/rules/`, and `knowledge/sql/` — no new artifact, no new tooling.\n\n## Hard rules — READ FIRST\n\n### Universal (apply to both modes)\n\n1. **Only add, never modify existing.** If you find an existing MDL description / relationship / rule that looks wrong, **do not edit it**. Surface it on the \"please fix manually\" list shown in Step 9.\n2. **Every MDL edit must validate.** Right after any MDL YAML change, run `wren context validate`. If it fails, **revert that single change** and feed the error back. Never leave a project in an invalid state.\n3. **Pre-draft every proposal.** Whether you're showing the draft to the user (grill) or applying it directly (auto-pilot), generate the concrete content — never lazy-ask \"what should the description say?\".\n4. **Be explicit about confidence.** In grill mode, open Lane 3 inference questions with \"I'm guessing — \". In auto-pilot, tag every Lane 3 inference and partial Lane 2 match in the Step 9 audit with confidence (high / med / low) and source.\n\n### Grill mode only\n\n5. **One question at a time. Grill relentlessly.** Walk every gap top-down, resolve one decision before moving to the next. Provide a recommended answer for every question.\n6. **Skip is final for this session.** No pending queue, no nagging next round. If the user wants to revisit, they re-run the skill.\n\n### Auto-pilot mode only\n\n7. **Drop into grill for three cases.** Always interrupt auto-pilot and ask the user when:\n   - (a) **Lane 2 conflict** — raw and current MDL disagree.\n   - (b) **High-blast-radius proposal (any lane)** — new cube, new view, new relationship, or new MDL metric/calculated column. These become public artifacts visible to every future agent session, so blast radius doesn't depend on whether the trigger was raw evidence (Lane 2) or inference (Lane 3).\n   - (c) **Lane 2 routing ambiguity** — you can't confidently pick a sink (MDL / `knowledge/rules/` / `knowledge/sql/` / `cubes/`).\n\n   Everything else: apply directly and log to the audit list.\n\n## Step 0 — Mode selection (before anything else)\n\nBefore touching the project or reading any file, ask the user which mode to run in. Lock the choice for the whole session — **no mid-session switching**; the user re-runs to change.\n\n> Two modes for this session:\n>\n> **a) Grill mode** — I walk every gap with you, one question at a time, proposing a draft and waiting for your accept / edit / skip. You stay in the driver's seat. Best when the raw material is sensitive, when you want to learn what I don't know about your project, or when you'd rather review than re-do.\n>\n> **b) Auto-pilot mode** — I read raw + current context, make my best inferences, and apply them. I'll only stop to grill you on (1) conflicts between raw and existing MDL and (2) high-blast-radius additions like new metrics, views, or relationships. The session ends with a full diff + confidence-tagged inference list for you to audit.\n>\n> Which? (a / b)\n\nRemember the choice as `MODE = grill | autopilot` and use it to branch Steps 6 and 9.\n\n## Preflight\n\n### Step 1 — Choose the Wren project\n\n**Always ask the user which project to enrich before doing anything else** — never assume cwd. A user can have several Wren projects and an ambient `~/.wren` profile that doesn't match the one they want to augment.\n\nOffer concrete hints in the question so the user can answer in one round-trip:\n\n```bash\n# Hint 1 — does cwd look like a project?\ntest -f wren_project.yml && pwd\n\n# Hint 2 — does ~/.wren/config.yml point at a default project?\ngrep -E '^project_path:' ~/.wren/config.yml 2>/dev/null\n```\n\nThen ask:\n\n> Which Wren project do you want me to augment?\n> a) `$PWD` (current directory)         ← if Hint 1 matched\n> b) `<path from ~/.wren/config.yml>`   ← if Hint 2 matched\n> c) something else — paste the absolute path\n\nAfter the user answers, lock the path in for the whole session:\n\n```bash\ncd <chosen-path>\ntest -f wren_project.yml || {\n  echo \"Error: <chosen-path> is not a Wren project (no wren_project.yml).\"\n  exit 1\n}\nwren context show >/dev/null 2>&1 || {\n  echo \"Error: wren context show failed — manifest may be invalid.\"\n  exit 1\n}\n```\n\nIf either check fails, stop and tell the user — suggest `wren skills get onboarding` if it's not a project, or `wren context validate` if the manifest is broken.\n\nFrom this point on, **every command and file path in this skill is relative to the chosen project root**. Do not switch projects mid-session — if the user wants to work a different project, end this session and re-run.\n\n### Step 2 — Detect semantic-memory availability\n\n```bash\nwren memory status 2>/dev/null | grep -q \"Backend: lancedb\"\n```\n\nWriting NL→SQL pairs (`wren memory store` → `knowledge/sql/*.md`) works **regardless** — that\nsink is always open. This checks which recall backend is active, i.e. whether the optional\n`memory` extra is installed (LanceDB = semantic; grep = dependency-free fallback):\n\n- Match (`Backend: lancedb`) → set `MEMORY_AVAILABLE = true`. Semantic recall and `wren memory fetch` are usable, and `wren memory index` builds an embedding index in Step 8.\n- No match (grep backend) → set `MEMORY_AVAILABLE = false`. Skip the semantic read/index paths below; pair writeback still happens via `wren memory store`, and grep recall still works.\n\n### Step 3 — Ensure raw/ folder exists\n\nFrom the project root (cwd is already there from Step 1):\n\n```bash\nmkdir -p raw\n```\n\nIf you just created it (the directory was empty or new):\n\n> I've created `raw/` at the project root. Drop anything you think helps explain this project's business context — PDFs, glossaries, handbooks, financial reports, data dictionaries, sample queries, code with comments, screenshots of dashboards, anything.\n>\n> **Heads-up:** the contents may be sensitive. Decide for yourself whether to commit `raw/` to git — I won't touch `.gitignore`.\n>\n> Tell me when you've added the files and I'll start reading.\n\nWait for the user to confirm before continuing.\n\n## Step 4 — Read everything\n\nRead both sides — the raw material and the current Wren context — before forming any opinion.\n\n### Raw\n\nRead every file under `raw/`. Use whatever capability your agent has natively (text, markdown, code, PDF). If you genuinely can't read a particular file, **tell the user once** which file and suggest converting it to text or pasting the relevant excerpt — then move on to the rest. Do **not** install extra Python packages, **do not** reach for new CLI subcommands.\n\n### Current Wren context\n\n| Source | Command |\n|---|---|\n| MDL (full) | `wren context show --output json` |\n| Business rules | `wren context instructions` (reads `knowledge/rules/` + any legacy `instructions.md`) |\n| Existing cubes (names) | `wren cube list` |\n| Existing cubes (measures + dimensions) | `wren cube describe <cube>` for each name above |\n| NL→SQL pairs | read `knowledge/sql/*.md` directly |\n| (Memory) stored pairs | `wren memory list -n 200 --output json` |\n| (Memory) schema as text | `wren memory describe` |\n\nThe memory rows only matter when `MEMORY_AVAILABLE = true`. Reading cubes is essential before any Lane 3 metric proposal — see `cube_proposals` for the duplication guard.\n\n## Step 4.5 — Ground-truth probe (grill mode default; auto-pilot opt-out)\n\nWhen raw is silent on a column's enum / unit / null / magic / time semantics, the catalog's column-local categories (#1, #2, #3, #5, #7 in `gap_catalog`) can often be settled directly by sampling distinct values from the live DB. Read `gap_catalog` before this step — its *Trigger* column tells you which columns are probe candidates.\n\n**Default policy by mode:**\n\n| Mode | Default | How to override |\n|---|---|---|\n| **Grill** | Probe on. Before the first query, ask the user once: \"I want to sample N columns with `LIMIT 30` each to find enum / sentinel / time-grain values — OK?\" Lock the answer for the session. | User says no → skip Step 4.5 entirely; rely on Lane 2 + Lane 3 instead. |\n| **Auto-pilot** | Probe off. The skill never queries the live DB in auto-pilot mode. | None — user must re-run in grill mode if probe would unblock high-confidence inferences. |\n\n**Candidate selection (no DB call yet):**\n\nA column is a probe candidate when *all* hold:\n\n- Description is empty OR does not yet contain the relevant `[tag]` (catalog write format).\n- Column type / name pattern matches catalog #1 (enum), #3 (NULL), #5 (magic), or #7 (time grain).\n- For #3 and #7, the description also lacks event-vs-record or TZ wording.\n\nCategories #2 (unit), #4, #6, #8, #9, #10 are **not** probable — `SELECT DISTINCT` doesn't reveal units, default filters, synonyms, external mappings, currency conventions, or canonical-table preferences. Those need raw evidence or human judgment.\n\n**Probe query:**\n\n```bash\nwren --sql \"SELECT DISTINCT <col> FROM <model> LIMIT 30\" --output json\n# For magic sentinels (catalog #5), also fetch min/max:\nwren --sql \"SELECT MIN(<col>) AS lo, MAX(<col>) AS hi FROM <model>\" --output json\n```\n\n- **≤ 30 distinct values returned** → enum / sentinel / grain candidate. Draft the `[tag]` line and surface to user (grill) with confidence \"med — probed values, semantics still inferred\".\n- **30 returned (LIMIT hit)** → cardinality too high; not an enum / sentinel candidate. Skip.\n- **Query fails** (permissions, connection, large-table timeout) → do not retry. Log the failure to the audit list, surface in Step 9, continue with Lane 2 + Lane 3 only.\n\n**Safety:**\n\n- Probe each (model, column) at most once per session.\n- Never probe a column that already has a matching `[tag]` line — Universal Rule 1.\n- Probe results stay in working memory; do not write them to disk.\n\n## Step 5 — Three gap-detection lanes (in your head, no artifact)\n\nHold all three lanes in working memory. Do not write a `gaps.yml`.\n\nBefore sweeping, load `gap_catalog` — the ten business-semantic categories the schema cannot carry. Each lane consumes the catalog differently: Lane 1 walks it as type-aware mechanical triggers, Lane 2 classifies each atomic raw claim into one of the 10 categories before routing, Lane 3 seeds inference prompts when a trigger fires but raw is silent.\n\n### Lane 1 — Structural coverage (mechanical)\n\nScan the current MDL and check:\n\n- Every model has a non-empty `properties.description`?\n- Every column has a description (at least for non-PK, non-FK ones)?\n- Every model has a `primary_key`?\n- Every model has at least one relationship (orphan models are suspicious)?\n- `knowledge/rules/` has real content beyond the scaffold default?\n- `knowledge/sql/` has at least a few canonical NL→SQL pairs?\n\nPlus, walk every column / model against `gap_catalog` triggers:\n\n- For each column matching catalog #1 / #2 / #3 / #5 / #7 triggers → is the corresponding `[tag]` line present in `properties.description`?\n- For each model with a soft-delete column (`deleted_at`, `is_active`, `archived_at`, etc.) → is there a `## Default filters` rule in `knowledge/rules/` covering it (catalog #4)?\n- For each lookalike table pair (e.g. `users` / `users_v3`) → is there a `## Canonical tables` rule (catalog #10)?\n- For each `*_currency` / `fx_rate` / external-system ID column → is the matching `## Currency` (#9) or `## External identifiers` (#8) section present?\n- Business terms in `knowledge/rules/` or raw that don't map verbatim to model / column names → catalog #6 `## Naming conventions` rule missing.\n\nEach unsatisfied check is a candidate. Combine with Step 4.5 probe results (if available) before moving to Lane 2.\n\n### Lane 2 — Claim-diff (raw vs current context)\n\nFor each raw file, internally extract 5–15 **atomic claims** — single statements that could be true or false, e.g. \"an order has exactly one customer\", \"user means type=default by default\", \"ARR equals MRR × 12 minus refunds\". Then for each claim, classify against the current Wren context:\n\n| Class | Meaning | Resolution outcome |\n|---|---|---|\n| **covered** | already reflected in MDL / instructions / pairs | skip |\n| **partial** | the topic exists but the wording / scope differs | propose tightening |\n| **new** | nothing in current context matches | route to a sink |\n| **conflict** | raw says A, current context says B | grill the user (both modes), **but do not edit existing** — surface for manual fix |\n\n### Lane 3 — Inference (your own guesses)\n\nAfter reading raw and the current MDL, propose additions the user did **not** literally state in raw but that would clearly help the agent later. Examples:\n\n- \"I see `quarterly_churn` referenced five times in `finance.pdf`. No existing cube covers it. Want me to add `cubes/quarterly_churn/metadata.yml` with measure = `COUNT(*) FILTER (WHERE churned_at IS NOT NULL) / NULLIF(COUNT(*), 0)`?\" — see `cube_proposals` for the YAML template and duplication guard.\n- \"Your support handbook keeps mentioning `core users` without defining it. Is this `users WHERE tier = 'premium'`? Want me to make a view?\"\n- \"The data dictionary says `events.payload` is JSON but the column has no description — let me draft one.\"\n\nFor any aggregation-shaped proposal (`SUM`, `COUNT`, `AVG`, \"by month / by status / per customer\" patterns), **default to a cube**. Run `wren cube list` + `wren cube describe` first to confirm no existing cube already covers the measure expression; if one does, skip the proposal and add a `knowledge/sql/` example pointing at the existing cube instead. The full decision tree, naming rules, and validation flow live in `cube_proposals`.\n\n**In grill mode, open every Lane 3 question with \"I'm guessing — \".** In auto-pilot, tag the audit entry with `agent inference` so the user sees you extrapolated.\n\n## Step 6 — Resolve gaps\n\nBranch on the `MODE` locked in Step 0.\n\n### Grill mode\n\nUse this conversational pattern for every gap surfaced in Lanes 1–3:\n\n> Interview the user relentlessly about every gap until we reach a shared understanding. Walk down each branch of the decision tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.\n>\n> Ask the questions one at a time.\n>\n> If a question can be answered by exploring the codebase or the raw files, do that instead of asking.\n\nFor every grill turn:\n\n1. State the gap and where it came from (Lane 1 / 2 / 3, and for Lane 2 quote the raw file + a short excerpt).\n2. Propose the concrete answer — draft the description, the rule, the SQL pair, the relationship.\n3. **Propose the sink** (\"I'll add this to `knowledge/rules/` as a rule\" / \"I'll add this to the `users` model description in MDL\").\n4. Let the user accept / edit / skip.\n5. On accept: write back (Step 7).\n6. On edit: apply their wording, then write back.\n7. On skip: drop it, move to the next gap. Do not requeue.\n\nWhen the user gives a curve-ball answer (\"actually we don't track that\") — pivot. The goal is shared understanding, not pushing a pre-built list.\n\n### Auto-pilot mode\n\nProcess every finding from Lanes 1–3 directly — **except** for the three escalation cases from Universal Rule 7:\n\n- **Lane 2 conflict** — drop into grill for this single question, never auto-resolve.\n- **Lane 3 proposing a new metric / view / relationship** — drop into grill before applying.\n- **Lane 2 routing ambiguous** — drop into grill for the sink choice only, then auto-apply.\n\nFor everything else (Lane 1 mechanical fixes, Lane 2 unambiguous new claims, Lane 3 low-impact description tweaks):\n\n1. Synthesize the concrete proposal (description / rule / SQL pair) using your best inference.\n2. Decide the sink using the routing table (Step 7).\n3. Write back.\n4. Run `wren context validate` immediately after any MDL edit. On failure: revert the single change and log the revert with the error.\n5. Append to the audit list: the finding, the sink, confidence tag (high / med / low), and source (raw file + excerpt for Lane 2, `agent inference` for Lane 3, `structural` for Lane 1).\n\nAuto-pilot does not pause for confirmation on each item — the user reviews the full diff + audit list in Step 9. They are the reviewer, not the gatekeeper.\n\n## Step 7 — Routing & writeback\n\nDecide the sink as part of the proposal (Step 6.3 in grill mode; Step 6.2 in auto-pilot), so the user can correct routing in grill mode and audit it in auto-pilot.\n\n| Finding type | Sink | How to write |\n|---|---|---|\n| Schema structure / relationship / view / model or column description | **MDL YAML** under `models/`, `views/`, `relationships.yml` | Edit the YAML file directly. For catalog #1 / #2 / #3 / #5 / #7 / PII, append a `[tag]` line to `properties.description` (prose first, then one tag per category). See `gap_catalog` for the exact tag format and triggers. |\n| Aggregation metric / named measure (with measures + dimensions) | **`cubes/<name>/metadata.yml`** | New file per cube. Default sink for any `SUM` / `COUNT` / `AVG` / ratio metric raw defines or Lane 3 infers. See `cube_proposals` for the YAML template, naming policy, duplication guard, and validation flow. Run `wren context validate` + `wren cube query --cube <name> --sql-only` after writing; revert on either failure. **Always escalates to grill in auto-pilot** (Universal Rule 7b). |\n| Default filter / implicit rule / business convention / naming convention / external mapping / currency / canonical table | **`knowledge/rules/`** | Append under the catalog-specified `##` section heading (#4 → `## Default filters`, #6 → `## Naming conventions`, #8 → `## External identifiers`, #9 → `## Currency`, #10 → `## Canonical tables`) inside a topic file under `knowledge/rules/` (e.g. `knowledge/rules/conventions.md`). Create the file/heading if absent; never modify existing text. |\n| NL→SQL example (canonical or ad-hoc) | **`knowledge/sql/`** | `wren memory store --nl \"...\" --sql \"...\" --tags \"source:enrich\"` — writes `knowledge/sql/<slug>.md` (committable) and indexes it when the extra is present. Works whether or not `MEMORY_AVAILABLE`. |\n\nCatalog-driven routing means every column-local proposal goes to the column's `properties.description` with a `[tag]` line; every cross-model rule goes to `knowledge/rules/` under a fixed heading. This keeps re-enrichment deterministic (greppable) and avoids inventing new sink locations.\n\n### After every MDL edit\n\n```bash\nwren context validate\n```\n\nIf it fails:\n1. Revert the single change you just made.\n2. Show the user the validation error (grill mode) or log it in the audit (auto-pilot).\n3. In grill mode, re-grill on that specific gap with the error as new context.\n4. In auto-pilot, mark the finding as \"revert: validation failed\" and move on.\n\n### Format reminders\n\n- NL→SQL pairs are written by `wren memory store`; each becomes a `knowledge/sql/<slug>.md` with YAML frontmatter (`nl`, `sql`, `source`, optional `datasource`/`tags`). Don't hand-write the files — use `wren memory store --tags \"source:enrich\"`.\n- MDL YAML uses snake_case keys (e.g. `primary_key`, `is_calculated`, `not_null`). `wren context build` converts to camelCase for `target/mdl.json`.\n- `knowledge/rules/` holds free-form markdown, one file per topic. Group rules by topic with `##` headings.\n\n## Step 8 — Session finalize\n\nAfter Step 6 ends (user says stop in grill mode, or every finding is processed in auto-pilot):\n\n```bash\nwren context build\n```\n\nThis recompiles `target/mdl.json` from the YAML edits.\n\nIf `MEMORY_AVAILABLE = true`:\n\n```bash\nwren memory index\n```\n\nThis rebuilds the index from the MDL schema items and the `knowledge/sql/` pairs. (Business\nrules in `knowledge/rules/` are read by `wren context instructions`, not embedded by `index`.)\n\n## Step 9 — Summary\n\n### Both modes — common section\n\nPrint a tight session report:\n\n```text\nWren Enrich Context — session summary  (mode: <grill|autopilot>)\n\nAdded:\n  MDL              : N model descriptions, N column descriptions, N relationships, N views\n                     by tag: [enum]=N [unit]=N [null]=N [magic]=N [time]=N [pii]=N\n  cubes            : N new (names: <list>)                                    via cubes/<name>/metadata.yml\n  knowledge/rules/ : N new rules across sections\n                     by section: Default filters=N | Naming conventions=N | External identifiers=N | Currency=N | Canonical tables=N\n  knowledge/sql/   : N new NL→SQL pairs                                       via wren memory store\n  Probe            : N columns sampled, M failed                             (grill mode only)\n\nPlease fix manually (we don't edit existing fields):\n  - models/orders/metadata.yml: existing description seems to contradict raw/glossary.pdf p.3\n  - relationships.yml: existing orders↔customers is MANY_TO_ONE but raw/data_dict.md p.7 says MANY_TO_MANY\n  - …\n```\n\n### Grill mode extras\n\nAppend:\n\n```text\nSkipped this session: N gaps (re-run /wren-enrich-context to revisit)\n```\n\n### Auto-pilot mode extras\n\nAppend a detailed audit so the user can sanity-check inferences:\n\n```text\nInferred items (please review):\n  high   | MDL model:orders.description            | from raw/glossary.pdf p.2 — \"Order = ...\"\n  high   | knowledge/rules/ rule                   | from raw/handbook.md §4 — \"default tier ...\"\n  med    | MDL column:users.signup_source.desc     | agent inference from raw/onboarding.md\n  low    | knowledge/sql/: \"weekly active customers\" | agent inference, no direct raw evidence\n\nValidation:\n  K successful applies, M reverted after wren context validate failed:\n    - relationships.yml: <error> → reverted\n\nEscalated to grill (raw vs MDL conflicts / high-impact additions):\n  - <count> items — see grill transcript above\n```\n\nThe user should be encouraged to skim the audit and either accept it as-is, manually tweak low-confidence rows, or re-run in grill mode if they want to revisit interactively.\n\n## Things to avoid\n\n- Do not write a `gaps.yml`, `state.yml`, or any other tracking artifact. The session lives entirely in conversation.\n- Do not modify any existing MDL field, `knowledge/rules/` rule, or `knowledge/sql/` pair — only append / add. Surface mismatches on the manual-fix list.\n- Do not install new Python packages (`pypdf`, `docling`, …) to read raw. Use what your agent already has; ask the user to convert files you can't open.\n- Do not auto-resolve a conflict between raw and current MDL — always grill the user, in **both** modes.\n- Do not present Lane 3 inferences as if they were quoted from raw. Open with \"I'm guessing — \" (grill) or tag `agent inference` (auto-pilot).\n- `wren memory store` works whether or not `MEMORY_AVAILABLE` — it always writes the `knowledge/sql/*.md` pair (and indexes it only when the extra is present). No need to fall back to another sink.\n- Do not commit anything to git. The user owns the commit decision.\n- Do not nag about skipped questions. Skip is skip for this session (grill mode only — auto-pilot has no skip concept).\n- Do not run `wren context build` after every single MDL edit — once at the end is enough. Do run `wren context validate` after every edit.\n- Do not assume `raw/` was created by `wren context init` — it isn't. This skill creates it.\n- Do not switch modes mid-session. The user re-runs to change mode.\n- Do not append a `[tag]` line if the same category tag already exists for that column — Universal Rule 1. Surface contradictions on the manual-fix list instead.\n- Do not invent new `knowledge/rules/` section headings. Stick to the five catalog-defined headings (`## Default filters`, `## Naming conventions`, `## External identifiers`, `## Currency`, `## Canonical tables`). Anything that doesn't fit goes on the manual-fix list.\n- Do not probe the live DB in auto-pilot mode. Step 4.5 is grill-only by default.\n- Do not propose a cube whose measure expression already exists in another cube on the same `base_object` — write a `knowledge/sql/` example pointing at the existing cube instead. See `cube_proposals` duplication guard.\n- Do not modify an existing cube YAML even when raw contradicts it — Universal Rule 1. Surface on the manual-fix list.\n- Do not write a new cube alongside an old MDL `metrics:` entry that already covers the same logic. Surface as \"consider migrating to cube\" on the manual-fix list.\n- Do not skip `wren cube query --cube <name> --sql-only` after creating a cube. Structural `wren context validate` doesn't catch unresolvable measure / dimension expressions.\n- In auto-pilot, do not auto-apply Lane 2 conflicts or new metric / view / relationship inferences — always drop into grill for those.\n\n## See also\n\n- `gap_catalog` — the ten business-semantic gap categories, with triggers, default sinks, and write formats. Read this before Step 4.5 and Step 5.\n- `cube_proposals` — decision tree for when to propose a cube vs view vs calculated column, the cube YAML template, naming policy, duplication guard, and validation flow. Read this before any Lane 3 aggregation-shaped proposal.\n\n<!-- File: core/wren/src/wren/skills_content/genbi/SKILL.md (Tokens: ~2094 | Category: anthropic-skill) -->\n---\nname: genbi\ndescription: \"Turn a Wren project's context layer into a shareable, browser-side GenBI web app and deploy it to the user's Vercel or Cloudflare account. Orchestrates the full flow: `wren genbi build` returns a project-hydrated build instruction, the agent authors the app from scratch into apps/<name>/, then register → verify → deploy produce a shareable URL. Use this skill whenever the user wants to: build a dashboard from their Wren project, make a shareable analytics app, deploy their context layer as a web app, host a GenBI app on Vercel or Cloudflare Pages, or asks for a 'genbi app'.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren GenBI App — Agent Workflow Guide\n\n> This guide is served by the `wren` CLI (`wren skills get genbi`), so it\n> always matches your installed wrenai version.\n\nTurn a Wren context layer into a shareable GenBI app — from a natural-language\nrequest to a public URL in one conversation.\n\n**Division of labor:** the CLI owns the authoritative build instruction (it\nknows the live project facts and the pinned `wren-core-wasm` version) and all\ndeterministic state (index, verify, deploy). You — the agent — author the app\ncode by following the instruction. Never hand-write `.wren/apps.yml`.\n\n## Preconditions\n\n1. A Wren project is discoverable (`wren_project.yml` in cwd/ancestors, or ask\n   for the path and pass `-p`).\n2. The context layer exists. If `target/mdl.json` is missing, `wren genbi\n   build` compiles it implicitly — no separate step needed.\n3. `wren` CLI ≥ the version that ships the `genbi` command group\n   (`wren genbi --help` works).\n\n## Workflow\n\n### 1. Resolve the app name and data mode\n\n- App name: short kebab-case derived from the request (e.g. `sales-overview`).\n- Data mode:\n  - `snapshot` (default) — data is bundled with the app as parquet/duckdb and\n    queried client-side. Fully serverless. Right for demos, reports, small\n    data, and dlt-pipeline output.\n  - `live` — the app calls back to the user's warehouse/API at view time.\n    Right for production-scale or always-fresh data. Requires a CORS-enabled\n    endpoint and carries strict no-credentials rules.\n- Ask the user ONLY if the choice is genuinely ambiguous.\n\n### 2. Get the build instruction\n\n```bash\nwren genbi build <name> --prompt \"<the user's request, verbatim>\" --data-mode <mode>\n```\n\nFor long or multi-line prompts use `--prompt-file <file>` or pipe to\n`--prompt -`. The command prints the authoritative build instruction —\nwasm wiring (pinned version, CDN load), the project's model/column inventory,\ndata-mode guidance, acceptance criteria, and the target folder. It writes no\napp files — the only thing it may touch is `target/mdl.json`, which it compiles\nfirst if missing (see Precondition 2).\n\n### 3. Author the app\n\nFollow the instruction exactly. Key conventions:\n\n- Write everything under `apps/<name>/` — never outside it.\n- Copy the compiled MDL into the app as `apps/<name>/mdl.json`.\n- Load `wren-core-wasm` from the CDN given in the instruction; never bundle\n  the ~68MB binary.\n- snapshot: export the data the dashboard needs into `apps/<name>/data/` as\n  parquet (`verify` requires at least one `.parquet`/`.duckdb` asset). See\n  **Snapshot data export** below for the recipe and where the data comes from.\n- live: write an endpoint-only connection config. NEVER inline credentials —\n  `verify` scans for them (best-effort) and `deploy` gates on `verify`, but\n  the rule is on you: a public static host exposes every shipped file.\n- Design the dashboard to actually answer the user's request: pick the right\n  charts/tables for the question, not a generic template.\n\n#### Snapshot data export\n\nThe CLI hands you the build instruction, but the snapshot bytes still have to\nbe exported into the app folder — that step is yours.\n\n**Where the data comes from:**\n\n- **DuckDB-backed project** (incl. anything loaded by the `dlt-connector`\n  skill — its pipelines always land in a `.duckdb` file): the project's DuckDB\n  file *is* your snapshot source. If the user is connecting SaaS data (HubSpot,\n  Stripe, Salesforce, …) and has no project yet, run the SaaS→project flow\n  first: `wren skills get dlt-connector`. Then come back here to ship it.\n- **Warehouse-backed project** (Postgres, BigQuery, Snowflake, …): run the\n  query/queries the dashboard needs through the MDL layer and write the result\n  to parquet. Keep snapshots small — snapshot mode is for demos/reports, not\n  full warehouse extracts; use `live` mode for large or always-fresh data.\n\n**Recipe (DuckDB → parquet):**\n\n```bash\n# from the project root; <db> is the project's DuckDB file (see wren_project.yml)\npython - <<'PY'\nimport duckdb\ncon = duckdb.connect(\"<db>.duckdb\", read_only=True)\ncon.execute(\n    \"COPY (SELECT * FROM <table>) \"\n    \"TO 'apps/<name>/data/<table>.parquet' (FORMAT parquet)\"\n)\nPY\n```\n\nOnly export the columns/rows the dashboard uses. The compiled `mdl.json` you\ncopied in keeps the context layer intact regardless of how you bundle data.\n\n### 4. Register and verify\n\n```bash\nwren genbi register <name> --data-mode <mode>\nwren genbi verify <name>\n```\n\nIf verify fails: fix the reported problems and re-run verify. Do NOT proceed\nto deploy on a failed verify. Offer `wren genbi open <name>` for a local\npreview before shipping.\n\n### 5. Deploy (only if the user asked for it)\n\n```bash\nwren genbi deploy <name> --provider vercel      # or cloudflare\n```\n\n- Preview deployment by default. **Confirm with the user before `--prod`.**\n- Tokens: the CLI discovers `VERCEL_TOKEN` / `CLOUDFLARE_API_TOKEN` from the\n  environment or `.env` files. If missing, ask the user to export it or add\n  it to the project `.env`. NEVER put a token on the command line.\n- Cloudflare also needs `CLOUDFLARE_ACCOUNT_ID` (env or `.env`), a token\n  scoped with Pages:Edit, and the `wrangler` CLI on PATH (or `npx` available)\n  — the adapter shells out to `wrangler pages deploy`. If it's missing, ask\n  the user to `npm install -g wrangler`.\n- Report the returned URL to the user. Re-deploying the same app updates the\n  same provider target.\n- **Verify the URL actually loads.** After deploying, fetch the URL — a\n  successful deploy can still return **HTTP 401/403** to outsiders because of\n  the provider's access protection (see below). Don't report a link as\n  \"shareable\" until you've confirmed it serves.\n\n### Vercel Deployment Protection (the 401 trap)\n\nNew Vercel projects ship with **Vercel Authentication** turned ON by default,\nso every deployment — preview *and* production — returns **401** to anyone not\nlogged into the owning Vercel account/team. The deploy itself succeeded; the\nURL is just gated.\n\n- To make the app publicly shareable, the user disables it in the Vercel\n  dashboard: **Project → Settings → Deployment Protection → Vercel\n  Authentication → Disabled** (or scope it to production only). This setting\n  is not controllable from `wren genbi deploy`; it's a one-time toggle per\n  project in Vercel.\n- If the user only needs a private link (viewable while logged into their\n  Vercel account), leaving protection on is fine — just tell them the link\n  won't work for logged-out visitors.\n\n## Safety boundaries\n\n- Never inline secrets/credentials into app files — the deploy target is a\n  public static host; anyone with the URL can read every file. `verify`\n  scans for inlined credentials, but treat it as best-effort\n  defense-in-depth, not a guarantee — never rely on it to catch a secret\n  you shouldn't have written in the first place.\n- Never pass tokens as CLI flags; they leak into shell history.\n- Confirm before production deploys.\n- All index state goes through `wren genbi register/remove` — never edit\n  `.wren/apps.yml` by hand.\n\n## Quick reference\n\n| Step | Command |\n| --- | --- |\n| Get build instruction | `wren genbi build <name> --prompt \"…\" [--data-mode snapshot\\|live]` |\n| Record the app | `wren genbi register <name> --data-mode <mode>` |\n| Preflight | `wren genbi verify <name>` |\n| Local preview | `wren genbi open <name>` |\n| Ship | `wren genbi deploy <name> --provider vercel\\|cloudflare [--prod]` |\n| Inventory | `wren genbi list` / `wren genbi remove <name>` |\n\n<!-- File: core/wren/src/wren/skills_content/generate-mdl/SKILL.md (Tokens: ~2976 | Category: anthropic-skill) -->\n---\nname: generate-mdl\ndescription: \"Generate a Wren MDL project by exploring a database with available tools (SQLAlchemy, database drivers, MCP connectors, or raw SQL). Guides agents through schema discovery, type normalization, and MDL YAML generation using the wren CLI. Use when: user wants to create or set up a new MDL, onboard a new data source, or scaffold a project from an existing database.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Generate Wren MDL — CLI Agent Workflow\n\nBuilds an MDL project by discovering database schema and converting it\ninto Wren's YAML project format. The agent uses whatever database tools\nare available in its environment for introspection; the wren CLI handles\ntype normalization, validation, and build.\n\nFor memory and query workflows after setup, run `wren skills get usage`.\n\n## Prerequisites\n\n- `wren` CLI installed (`pip install \"wrenai[<datasource>]\"`)\n- A working database connection (credentials available to the agent)\n- A connection profile (set up via `wren profile add`) or connection info ready\n\n## Alternative — import from an OpenMetadata catalog\n\nIf the user's team already catalogs this database in OpenMetadata, import\nfrom the catalog instead of introspecting the live database. The import\ncarries curated table/column descriptions, tags, owners, and foreign-key\nconstraints (as `relationships.yml`), so the join-inference work in Phase 4\nis mostly done:\n\n```bash\nwren context import openmetadata --host <om_url> --token <om_token> \\\n  --service <om_service> --database <db> --schema <schema> --dry-run\nwren context import openmetadata --host <om_url> --token <om_token> \\\n  --service <om_service> --database <db> --schema <schema>\n```\n\nAsk the user for the OpenMetadata URL, a read-only bot or personal access\ntoken, and the service name as it appears in OpenMetadata. `--table`\n(repeatable) narrows the import. Without `--force` the command only adds\nfiles that do not exist and lists the collisions it left untouched; `--force`\nregenerates from OpenMetadata and discards local edits to generated files —\nconfirm with the user before using it. Relationships come from foreign keys\nonly; review whether additional joins are needed (Phase 4), then bind a\nprofile and finish with Phase 5 (`wren context validate` → `wren context build`).\n\n## Phase 0 — Detect existing project\n\n**Goal:** If the current directory is already inside a wren project, let the user decide how to proceed.\n\nCheck whether `wren_project.yml` exists in the current working directory\n(or any parent up to the repository root). If found:\n\n1. Tell the user that an existing wren project was detected and show its path.\n2. Ask:\n   - **Reset** — wipe the existing project (`models/`, `views/`,\n     `relationships.yml`, `knowledge/`, and rebuild `wren_project.yml`)\n     and regenerate from scratch in the same directory.\n   - **New path** — keep the existing project untouched and choose a\n     different directory for the new project. Ask the user for the new path,\n     then `wren context init --path <new_path>` and continue from Phase 1\n     using that path.\n\nIf no existing project is detected, proceed directly to Phase 1.\n\n## Phase 1 — Establish connection and scope\n\n**Goal:** Confirm the agent can reach the database and agree on scope with the user.\n\n1. Verify connectivity using whichever tool is available:\n   - If SQLAlchemy: `engine.connect()` test\n   - If database driver: simple query like `SELECT 1`\n   - If wren profile exists: `wren profile debug` to check config\n   - If raw SQL via wren: `wren --sql \"SELECT 1\"` (requires profile or connection file)\n\n2. Ask the user:\n   - Which **schema(s)** or **dataset(s)** to include (skip if only one exists)\n   - Whether to include **all tables** or a subset\n   - The **datasource type** for wren (e.g., `postgres` (including Aurora), `mysql` (including Aurora), `bigquery`, `snowflake`) — needed for type normalization dialect\n\n## Phase 2 — Discover schema\n\n**Goal:** Collect table names, column names, column types, and constraints.\n\nUse whatever introspection method is available. Here are common approaches\nranked by convenience:\n\n### Option A: SQLAlchemy (recommended if available)\n\n```python\nfrom sqlalchemy import create_engine, inspect\n\nengine = create_engine(connection_url)\ninspector = inspect(engine)\n\ntables = inspector.get_table_names(schema=\"public\")\n\nfor table in tables:\n    columns = inspector.get_columns(table, schema=\"public\")\n    # columns → [{\"name\": \"id\", \"type\": INTEGER(), \"nullable\": False, ...}]\n\n    pk = inspector.get_pk_constraint(table, schema=\"public\")\n    # pk → {\"constrained_columns\": [\"id\"], \"name\": \"orders_pkey\"}\n\n    fks = inspector.get_foreign_keys(table, schema=\"public\")\n    # fks → [{\"constrained_columns\": [\"customer_id\"],\n    #          \"referred_table\": \"customers\",\n    #          \"referred_columns\": [\"id\"]}]\n```\n\n### Option B: Database-specific driver\n\n- **psycopg / asyncpg (Postgres):** Query `information_schema.columns` and `information_schema.table_constraints`\n- **google-cloud-bigquery:** `client.list_tables()`, `client.get_table()` → `table.schema`\n- **snowflake-connector-python:** `SHOW COLUMNS IN TABLE`, `SHOW PRIMARY KEYS IN TABLE`\n- **clickhouse-driver:** `DESCRIBE TABLE`, `system.tables`\n\n### Option C: Raw SQL via wren\n\nIf no driver is available but a wren profile is configured, query\n`information_schema` through wren itself:\n\n```bash\nwren --sql \"SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'\" -o json\nwren --sql \"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'orders'\" -o json\n```\n\nNote: this goes through the MDL layer, so it only works if you already\nhave a minimal MDL or if the database supports `information_schema` as\nregular tables. For bootstrapping from zero, Option A or B is preferred.\n\n## Phase 3 — Normalize types\n\n**Goal:** Convert raw database types to wren-core-compatible types.\n\n### Python import (recommended for batch processing)\n\n```python\nfrom wren.type_mapping import parse_type, parse_types\n\n# Single type\nnormalized = parse_type(\"character varying(255)\", \"postgres\")  # → \"VARCHAR(255)\"\n\n# Batch — entire table at once\ncolumns = [\n    {\"column\": \"id\", \"raw_type\": \"int8\"},\n    {\"column\": \"name\", \"raw_type\": \"character varying\"},\n    {\"column\": \"total\", \"raw_type\": \"numeric(10,2)\"},\n]\nnormalized_cols = parse_types(columns, dialect=\"postgres\")\n# Each dict now has a \"type\" key with the normalized value\n```\n\n### CLI (if Python import not available)\n\nSingle type:\n```bash\nwren utils parse-type --type \"character varying(255)\" --dialect postgres\n# → VARCHAR(255)\n```\n\nBatch (stdin JSON):\n```bash\necho '[{\"column\":\"id\",\"raw_type\":\"int8\"},{\"column\":\"name\",\"raw_type\":\"character varying\"}]' \\\n  | wren utils parse-types --dialect postgres\n```\n\n## Phase 4 — Scaffold and write MDL project\n\n**Goal:** Create the YAML project structure.\n\n### Step 1 — Initialize project\n\n```bash\nwren context init --path /path/to/project\n```\n\nThis creates:\n```text\nproject/\n├── wren_project.yml\n├── models/              # business-facing tables/models\n├── views/               # named SQL statements\n├── cubes/               # pre-aggregation cubes (measures + dimensions)\n├── relationships.yml\n└── knowledge/           # business rules (rules/) + NL→SQL pairs (sql/)\n```\n\n> **When to define cubes:** If the user asks aggregation questions like\n> \"revenue by month\" or \"top customers\", define cubes alongside models —\n> they give agents a structured query API instead of forcing them to\n> hand-write `GROUP BY` / `DATE_TRUNC` SQL. See the\n> [Cube guide](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/cubes.md).\n\n> **IMPORTANT: `catalog` and `schema` in `wren_project.yml`**\n>\n> These are Wren Engine's internal namespace — they are NOT the database's\n> native catalog or schema. Keep the defaults (`catalog: wren`, `schema: public`)\n> unless you are intentionally configuring a multi-project namespace.\n>\n> Your database's actual catalog/schema is specified per-model in `table_reference`\n> (see Step 2). Do not copy database catalog/schema values into `wren_project.yml`.\n\n### Step 2 — Write model files\n\nFor each table, create a YAML file under `models/`. Use snake_case\nnaming (the build step converts to camelCase automatically).\n\n```yaml\n# models/orders/metadata.yml\nname: orders\ntable_reference:\n  catalog: \"\"           # database catalog (empty string if not applicable;\n                        #   for DuckDB, use the DB file name without extension,\n                        #   e.g. jaffle_shop.duckdb → catalog: jaffle_shop)\n  schema: public        # database schema (this IS the DB schema)\n  table: orders         # database table name\nprimary_key: order_id\ncolumns:\n  - name: order_id\n    type: INTEGER\n    not_null: true\n  - name: customer_id\n    type: INTEGER\n  - name: total\n    type: \"DECIMAL(10, 2)\"\n  - name: status\n    type: VARCHAR\n    properties:\n      description: \"Order status: pending, shipped, delivered, cancelled\"\n```\n\n### Step 3 — Write relationships\n\nFrom foreign key constraints discovered in Phase 2:\n\n```yaml\n# relationships.yml\nrelationships:\n  - name: orders_customers\n    models:\n      - orders\n      - customers\n    join_type: many_to_one\n    condition: \"orders.customer_id = customers.customer_id\"\n```\n\nJoin type mapping:\n- FK table → PK table: `many_to_one`\n- PK table → FK table: `one_to_many`\n- Unique FK: `one_to_one`\n- Junction table: `many_to_many`\n\nIf no foreign keys were found, infer from naming conventions:\n- Column `<table>_id` or `<table_singular>_id` → likely FK to `<table>`\n- Ask the user to confirm inferred relationships\n\n### Step 4 — Add descriptions (optional but valuable)\n\nAsk the user to describe:\n- Each model (1-2 sentences about what the table represents)\n- Key columns (especially calculated fields or non-obvious names)\n\nThese descriptions are indexed by `wren memory index` and significantly\nimprove LLM query accuracy.\n\n## Phase 5 — Validate and build\n\n```bash\n# Validate YAML structure and integrity\nwren context validate --path /path/to/project\n\n# If strict mode is desired:\nwren context validate --path /path/to/project --strict\n\n# Build JSON manifest\nwren context build --path /path/to/project\n\n# Verify against database\nwren --sql \"SELECT * FROM <model_name> LIMIT 1\"\n```\n\nIf validation fails, fix the reported issues and re-run. Common errors:\n- Duplicate model/column names\n- Missing primary key\n- Relationship referencing non-existent model\n- Invalid column type (try re-running through `parse_type`)\n\n## Phase 6 — Initialize memory\n\n```bash\n# Index schema (generates seed NL-SQL examples automatically)\nwren memory index\n\n# Verify\nwren memory status\n```\n\nAfter this step, `wren memory fetch` and `wren memory recall` are\noperational. See `wren skills get usage` for query workflows.\n\n## Phase 7 — Iterate with the user\n\nThe initial MDL is a starting point. Improve it by:\n- Adding calculated columns based on business logic\n- Adding views for common query patterns\n- Refining descriptions based on actual query usage\n- Adding access control (RLAC/CLAC) if needed\n\nEach change follows: edit YAML → `wren context validate` →\n`wren context build` → `wren memory index`.\n\n## Quick reference\n\n| Task | Command / Method |\n|------|-----------------|\n| Discover tables | Agent's own tools (SQLAlchemy, driver, raw SQL) |\n| Discover columns + types | Agent's own tools |\n| Discover constraints | Agent's own tools |\n| Normalize types (Python) | `from wren.type_mapping import parse_type` |\n| Normalize types (CLI) | `wren utils parse-type --type T --dialect D` |\n| Normalize types (batch) | `wren utils parse-types --dialect D < columns.json` |\n| Scaffold project | `wren context init` |\n| Write models | Create `models/<name>/metadata.yml` |\n| Write relationships | Edit `relationships.yml` |\n| Validate | `wren context validate` |\n| Build manifest | `wren context build` |\n| Test query | `wren --sql \"SELECT * FROM <model> LIMIT 1\"` |\n| Index memory | `wren memory index` |\n\n## Things to avoid\n\n- Do not hardcode database-specific type strings in MDL — always normalize via `parse_type`\n- Do not skip validation before build — invalid YAML produces broken manifests silently\n- Do not guess column types — introspect from the actual database\n- Do not write relationships without confirming join conditions — wrong conditions cause silent query errors\n- Do not skip `wren memory index` after build — stale indexes degrade recall quality\n\n<!-- File: core/wren/src/wren/skills_content/onboarding/SKILL.md (Tokens: ~2340 | Category: anthropic-skill) -->\n---\nname: onboarding\ndescription: \"Onboard a user to Wren Engine end-to-end. Walks through environment checks, project scaffolding, connection configuration via .env, and first query. Use when: user wants to install Wren Engine, set up a new data source connection, or bootstrap a new project from scratch. Triggers: '/wren-onboarding', 'install wren', 'set up wren engine', 'wren onboarding', 'connect new database to wren'.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren Onboarding — Agent Workflow\n\nThis skill walks the agent through onboarding — environment checks, project scaffolding, profile creation, MDL generation, and first query. **Procedural details, per-datasource setup notes, and the troubleshooting playbook live in the docs**, not here. The skill's job is to enforce the agent-side rules (one step per turn, never ask for credentials in chat) and to dispatch the agent to the right doc / sibling skill at each step.\n\nReference docs (the skill points to these — never duplicate their content):\n- [`docs/core/get_started/installation.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/get_started/installation.md) — CLI install + skill install\n- [`docs/core/guides/connect.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md) — full connection procedure, **per-datasource setup notes, complete troubleshooting playbook**\n- [`docs/core/get_started/quickstart.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/get_started/quickstart.md) — bundled `jaffle_shop` demo\n\n## Mode of operation — READ THIS FIRST\n\n**One step per round-trip.** Each numbered step below is its own turn: explain briefly, ask **only** what the step needs, run the command(s), confirm, move on.\n\n- ❌ **Never collect information for future steps upfront.** Do not ask for project name + database type + credentials in one message.\n- ❌ **Never ask for credentials in chat — not host, port, user, password, tokens, anything.** Credentials always go through `.env`. The user fills the file in their editor; the agent never sees the values.\n- ❌ **Never query the database before MDL is built** via `wren skills get generate-mdl`.\n- ❌ **Never invent connection field names.** Always run `wren docs connection-info <ds>` to see the real fields — it's introspected from the live Pydantic schema, so it's always correct.\n- ✅ Wait for each command to finish, report its output in plain language, then move on.\n- ✅ For any error, consult `connect.md#troubleshooting` and surface the relevant section to the user — don't carry a copy of the playbook here.\n\n## Preflight (environment only — no user questions about the project)\n\nRead-only checks. Report findings, do **not** ask about project / credentials / datasource yet.\n\n1. `python3 --version` — requires Python 3.11+. If older, ask the user to upgrade and stop.\n2. Check virtualenv: `python3 -c \"import sys; print(sys.prefix != sys.base_prefix)\"`. If `False`, offer to create one (`python3 -m venv .venv && source .venv/bin/activate`). PEP 668 systems will need this.\n3. `wren --version` — if already installed, confirm before reinstalling.\n4. `pwd` — record it. Don't ask where the project should live yet.\n\nReport findings as a 4-bullet list, then continue.\n\n## Early branch — demo or own database?\n\n> \"Try the bundled `jaffle_shop` demo first (~30s, no DB needed), or connect your own database?\"\n\n- **demo** → point at [`quickstart.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/get_started/quickstart.md) and stop this skill.\n- **own DB** → continue.\n\n## Step 1 — Collect project name + database type\n\nThese two are the only thing Step 2 needs; ask both together so the user has a clean handoff:\n\n> \"Two things before I scaffold:\n> 1. **Project name** — I'll create `~/<name>/` and `cd` into it.\n> 2. **Database type** — run `wren docs connection-info` (no argument) to see the full list, or pick a common one: `postgres` (use for Aurora PostgreSQL), `mysql` (use for Aurora MySQL), `bigquery`, `snowflake`, `clickhouse`, `trino`, `duckdb`, …\"\n\nWait for both. Don't ask for credentials.\n\n## Step 2 — Workspace + .env setup (batch)\n\nSide effects: creates `~/<project>/`, installs `wrenai[<ds>,main]`, generates an empty `.env` template. The project files (`wren_project.yml` etc.) come later in Step 3.5 — at this point we only have a directory with credentials waiting to be filled.\n\nRun as a batch — report each command briefly, then end with one \"please fill `.env`\" ask:\n\n1. `mkdir -p ~/<project> && cd ~/<project>`.\n2. `pip install \"wrenai[<ds>,main]\"`. For datasource-specific install gotchas (macOS mysql, etc.), see [`connect.md#per-datasource-setup-notes`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md).\n3. **Generate the `.env` template by introspecting the connector**:\n\n   ```bash\n   wren docs connection-info <ds> --format md\n   ```\n\n   Use the field list to write `.env` with `<DS>_<FIELD>=` keys (UPPER_SNAKE), values **empty**. Example for postgres:\n\n   ```ini\n   POSTGRES_HOST=\n   POSTGRES_PORT=5432\n   POSTGRES_DATABASE=\n   POSTGRES_USER=\n   POSTGRES_PASSWORD=\n   ```\n\n   Special encodings (BigQuery base64, Snowflake account format, Athena AWS creds, etc.) are documented in [`connect.md#per-datasource-setup-notes`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md). Surface the relevant section to the user verbatim — don't paraphrase.\n\n4. Add `.env` to `.gitignore` if the project is a git repo. Suggest `chmod 600 .env`.\n5. Tell the user: `.env` is at `<path>`, please fill every value and reply **\"done\"**.\n\n## Step 3 — Create the connection profile\n\nOnly after the user replies \"done\".\n\nWrite `/tmp/conn.yml` with **every field as a `${VAR}` placeholder** matching the `.env` keys you generated in Step 2:\n\n```yaml\ndatasource: <ds>\nhost: ${<DS>_HOST}\nport: ${<DS>_PORT}\n# … one line per field from `wren docs connection-info <ds>`\n```\n\nThen:\n\n```bash\nwren profile add <project> --from-file /tmp/conn.yml\n```\n\nValidation runs automatically. The CLI overwrites profiles silently — there is no `--force` flag.\n\n- ✓ **Success** → continue to Step 3.5.\n- ⚠ **Any warning** → consult [`connect.md#troubleshooting`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md) for the exact symptom (missing secret, driver auth failure, ValidationError, unreachable host, …) and tell the user what to fix.\n\n## Step 3.5 — Scaffold the project\n\n```bash\nwren context init --empty\n```\n\nRefuses to overwrite an existing `wren_project.yml`. Creates the project directory layout (`models/`, `views/`, `relationships.yml`, `knowledge/` (rules + sql), `AGENTS.md`).\n\n## Step 3.6 — Bind the profile to the project\n\n```bash\nwren context set-profile <project>\n```\n\nWrites both `profile: <project>` and `data_source: <ds>` into `wren_project.yml` (data_source is taken from the profile we just validated, so it's guaranteed correct). Future CLI commands and the SDK resolve the connection deterministically — independent of which profile is globally active.\n\nThis step also future-proofs the project for multi-project setups: once the binding is recorded, switching `wren profile switch` elsewhere never breaks this project's queries.\n\n## Step 4 — Generate MDL (hand off)\n\n> ⚠️ The agent **must** build MDL before any data query. Queries against tables not in MDL will fail.\n\nRun `wren skills get generate-mdl` and follow it. It walks the agent through table introspection, type normalization, and YAML generation. When it finishes, return here and run:\n\n```bash\nwren context validate\nwren context build\n```\n\nReport the model count and any validate warnings.\n\n**Memory recommendation**: count models with `wren context show | grep -c '^model:'`. If `>= 200`, suggest `pip install \"wrenai[memory]\"` + `wren memory index` (~800 MB). If `< 200`, skip.\n\n## Step 5 — Ready to explore (hand off)\n\nSuggest 2–3 NL questions based on the discovered tables (e.g. for an orders schema: \"How many orders last month?\", \"Top 5 customers by total\"). Then end this skill: for day-to-day querying the agent should run `wren skills get usage`.\n\n## Cross-skill routing\n\n| Trigger | Skill |\n|---------|-------|\n| User mentions a SaaS source (HubSpot, Stripe, Salesforce, GitHub, Slack, …) | `wren skills get dlt-connector` |\n| User has a connected DB but no MDL yet | `wren skills get generate-mdl` |\n| User has MDL ready, wants to query | `wren skills get usage` |\n| Anything else from-scratch | `wren skills get onboarding` (this skill) |\n\n## On error\n\nDon't carry an error playbook here — surface [`connect.md#troubleshooting`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md) sections to the user. The doc covers:\n\n- `wren: command not found`\n- `pip install … externally-managed-environment`\n- Missing secret (`MissingSecretError`)\n- Driver authentication failures\n- Pydantic `ValidationError` / unknown datasource\n- Connection refused / firewall / cloud DB IP allow-list\n- `wren context validate` warning categories\n\nIf you hit something not in the playbook, tell the user:\n\n> \"I hit an error I don't know how to fix: `<error>`.\n> See <https://docs.getwren.ai/oss/introduction> or open an issue at <https://github.com/Canner/WrenAI/issues>.\"\n\n<!-- File: core/wren/src/wren/skills_content/usage/SKILL.md (Tokens: ~4194 | Category: anthropic-skill) -->\n---\nname: usage\ndescription: \"Wren Engine CLI workflow guide for AI agents. Answer data questions end-to-end using the wren CLI: gather schema context, recall past queries, write SQL through the MDL semantic layer, execute, and learn from confirmed results. Use when: user asks a data question, requests a report or analysis, asks about metrics, revenue, customers, orders, trends, or any business data; user says 'how many', 'show me', 'what is the', 'top N', 'compare', 'trend', 'growth', 'breakdown'; user wants to explore, analyze, filter, aggregate, or summarize data from a database; agent needs to query data, connect a data source, handle errors, or manage MDL changes via the wren CLI.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n\n# Wren Engine CLI — Agent Workflow Guide\n\n> This guide is served by the `wren` CLI (`wren skills get usage`), so it always matches your installed wrenai version. Pull the deeper reference docs with `wren skills get usage --full`.\n\n## Preflight — Verify environment and installation\n\n**Goal:** Ensure the `wren` CLI is available before entering any workflow.\n\n### Step 1 — Check Python virtual environment\n\nRun `python -c \"import sys; print(sys.prefix)\"` (or equivalent) to determine\nwhether a virtual environment is active.\n\n- If **no venv is active**, warn the user and ask whether to:\n  - Create one (e.g., `python -m venv .venv && source .venv/bin/activate`)\n  - Continue without a venv (not recommended — may pollute global packages)\n\n### Step 2 — Check if the `wren` CLI is installed\n\nRun `wren --version`. If the command is not found or errors:\n\n1. Tell the user that the `wren` CLI is not installed.\n2. Ask if you should help install it.\n3. If the user agrees, determine the **datasource extra** to install:\n\n   **Auto-detect from project:** Check whether the current directory is inside\n   a wren project (look for `wren_project.yml` up to the repository root).\n   If found, read the active profile with `cat ~/.wren/profiles.yml` or look\n   for a datasource hint in the project's profile configuration. Extract the\n   datasource type from there.\n\n   **Ask the user:** If no project is detected or no datasource can be\n   inferred, ask the user which database they plan to connect to. Valid\n   extras: `postgres` (for Aurora Postgres), `mysql` (for Aurora MySQL), `bigquery`, `snowflake`, `clickhouse`,\n   `trino`, `mssql`, `databricks`, `redshift`, `spark`, `athena`, `oracle`.\n   DuckDB is included by default — no extra needed.\n\n4. Install with the detected or chosen extra:\n   ```bash\n   # DuckDB (no extra needed)\n   pip install \"wrenai\"\n\n   # Other datasources\n   pip install \"wrenai[<datasource>]\"\n   ```\n   To also enable semantic memory, interactive prompts, and web UI (recommended):\n   ```bash\n   pip install \"wrenai[<datasource>,main]\"\n   # or for DuckDB:\n   pip install \"wrenai[main]\"\n   ```\n\n5. Verify: `wren --version`\n\nIf `wren --version` succeeds, proceed to the relevant workflow below.\n\n---\n\nThe `wren` CLI queries databases through an MDL (Model Definition Language) semantic layer. You write SQL against model names, not raw tables. The engine translates to the target dialect.\n\nTwo things drive everything:\n- **Profile** — database connection + datasource type, managed via `wren profile` (stored in `~/.wren/profiles.yml`)\n- **Project** — MDL model definitions in YAML, compiled to `target/mdl.json` via `wren context build`\n\nThe CLI reads the active profile for connection info and datasource. Use `wren profile list` to see which profile is active, `wren profile switch <name>` to change it. `dry-plan` also accepts `--datasource` / `-d` for transpile-only use without a profile.\n\nFor memory-specific decisions, see the `memory` reference (run `wren skills get usage --full`).\nFor SQL syntax, CTE-based modeling, and error diagnosis, see the `wren-sql` reference (run `wren skills get usage --full`).\nFor project structure, MDL field definitions, and CLI workflow details, see the [documentation](https://github.com/Canner/WrenAI/tree/main/docs/core).\n\n---\n\n## Workflow 1: Answering a data question\n\n### Step 1 — Gather context\n\n| Situation | Command |\n|-----------|---------|\n| Default | `wren memory fetch -q \"<question>\"` |\n| Need specific model's columns | `wren memory fetch -q \"...\" --model <name> --threshold 0` |\n| Memory not installed | Read `target/mdl.json` in the project directory, or run `wren context show` |\n\nIf this is the first query in the conversation, also run:\n\n```text\nwren context instructions\n```\n\nIf it returns content, treat it as **rules that override defaults** — apply them to all subsequent queries in this session.\n\n### Step 2 — Recall past queries\n\n```bash\nwren memory recall -q \"<question>\" --limit 3\n```\n\nUse results as few-shot examples. Skip if empty.\n\n### Step 2.5 — Assess complexity (before writing SQL)\n\nIf the question involves **any** of the following, consider decomposing:\n- Multiple metrics or aggregations (e.g., \"churn rate AND expansion revenue\")\n- Multi-step calculations (e.g., \"month-over-month growth rate\")\n- Comparisons across segments (e.g., \"by plan tier, by region\")\n- Time-series analysis requiring baseline + change (e.g., \"retention curve\")\n\n**Decomposition strategy:**\n1. Identify the sub-questions (e.g., \"total subscribers at start\" + \"subscribers who cancelled\" → churn rate)\n2. For each sub-question:\n   - `wren memory recall -q \"<sub-question>\"` — check if a similar pattern exists\n   - Write and execute a simple SQL\n   - Note the result\n3. Combine sub-results to answer the original question\n\n**When NOT to decompose:**\n- Single-table aggregation with GROUP BY — just write the SQL\n- Simple JOINs that the MDL relationships already define\n- Questions where `memory recall` returns a near-exact match\n\nThis is a judgment call, not a rigid rule. If you're confident in a single\nquery, go ahead. Decompose when the SQL would be hard to debug if it fails.\n\n### Step 3 — Write, verify, and execute SQL\n\n**For simple queries** (single table or simple MDL-defined JOINs, straightforward aggregation):\nExecute directly:\n```bash\nwren --sql 'SELECT c_name, SUM(o_totalprice) FROM orders\nJOIN customer ON orders.o_custkey = customer.c_custkey\nGROUP BY 1 ORDER BY 2 DESC LIMIT 5'\n```\n\n**For complex queries** (non-trivial JOINs not covered by MDL relationships, subqueries, multi-step logic):\nVerify first with dry-plan:\n```bash\nwren dry-plan --sql 'SELECT ...'\n```\n\nCheck the expanded SQL output:\n- Are the correct models and columns referenced?\n- Do the JOINs match expected relationships?\n- Are CTEs expanded correctly?\n\nIf the expanded SQL looks wrong, fix before executing.\nIf it looks correct, proceed:\n```bash\nwren --sql 'SELECT ...'\n```\n\n**SQL rules:**\n- Target MDL model names, not database tables\n- Write dialect-neutral SQL — the engine translates\n\n### Step 4 — Store and continue\n\nAfter successful execution, **store the query by default**:\n\n```bash\nwren memory store --nl \"<user's original question>\" --sql \"<the SQL>\"\n```\n\n**Skip storing only when:**\n- The query failed or returned an error\n- The user said the result is wrong\n- The query is exploratory (`SELECT * ... LIMIT N` without analytical clauses)\n- There is no natural language question — just raw SQL\n- The user explicitly asked not to store\n\nThe CLI auto-detects exploratory queries — if you see no store hint\nafter execution, the query was classified as exploratory.\n\n| Outcome | Action |\n|---------|--------|\n| User confirms correct | Store |\n| User continues with follow-up | Store, then handle follow-up |\n| User says nothing (but question had clear NL description) | Store |\n| User says wrong | Do NOT store — fix the SQL |\n| Query error | See Error recovery below |\n\n---\n\n## Workflow 2: Error recovery\n\n### \"table not found\"\n\n1. Verify model name: `wren memory fetch -q \"<name>\" --type model --threshold 0`\n2. Check MDL exists: `ls target/mdl.json` (or `wren context show`)\n3. Verify column: `wren memory fetch -q \"<column>\" --model <name> --threshold 0`\n\n### Connection error\n\n1. Check active profile: `wren profile debug`\n2. Verify datasource and connection fields are correct\n3. Test: `wren --sql \"SELECT 1\"`\n4. Valid datasource values: `postgres` (for Aurora Postgres), `mysql` (for Aurora MySQL), `bigquery`, `snowflake`, `clickhouse`, `trino`, `mssql`, `databricks`, `redshift`, `spark`, `athena`, `oracle`, `duckdb`\n5. If no profile exists, create one: `wren profile add --ui` (or `--interactive` / `--from-file`)\n\n### SQL syntax / planning error (enhanced)\n\n#### Layer 1: Identify the failure point\n\n```bash\nwren dry-plan --sql \"<failed SQL>\"\n```\n\n| dry-plan result | Failure layer | Next step |\n|-----------------|---------------|-----------|\n| dry-plan fails | MDL / semantic | → Layer 2A |\n| dry-plan succeeds, execution fails | DB / dialect | → Layer 2B |\n\n#### Layer 2A: MDL-level diagnosis (dry-plan failed)\n\nThe dry-plan error message tells you exactly what's wrong:\n\n| Error pattern | Diagnosis | Fix |\n|---------------|-----------|-----|\n| `column 'X' not found in model 'Y'` | Wrong column name | `wren memory fetch -q \"X\" --model Y --threshold 0` to find correct name |\n| `model 'X' not found` | Wrong model name | `wren memory fetch -q \"X\" --type model --threshold 0` |\n| `ambiguous column 'X'` | Column exists in multiple models | Qualify with model name: `ModelName.column` |\n| Planning error with JOIN | Relationship not defined in MDL | Check available relationships in context |\n\n**Key principle**: Fix ONE issue at a time. Re-run dry-plan after each fix\nto see if new errors surface.\n\n#### Layer 2B: DB-level diagnosis (dry-plan OK, execution failed)\n\nThe DB error + dry-plan output together pinpoint the issue:\n\n1. Read the dry-plan expanded SQL — this is what actually runs on the DB\n2. Compare with the DB error message:\n\n| Error pattern | Diagnosis | Fix |\n|---------------|-----------|-----|\n| Type mismatch | Column type differs from assumed | Check column type in context, add explicit CAST |\n| Function not supported | Dialect-specific function | Use dialect-neutral alternative |\n| Permission denied | Table/schema access | Check connection credentials |\n| Timeout | Query too expensive | Simplify: reduce JOINs, add filters, LIMIT |\n\n**For small models**: If the error message is unclear, try simplifying\nthe query to the smallest failing fragment. Execute subqueries independently\nto isolate which part fails.\n\nFor the CTE rewrite pipeline and additional error patterns, see the `wren-sql` reference (run `wren skills get usage --full`).\n\n---\n\n## Workflow 3: Connecting a new data source\n\n1. Add a profile: `wren profile add --ui` (or `--interactive` / `--from-file`)\n2. Test connection: `wren profile debug`\n3. Test query: `wren --sql \"SELECT 1\"`\n4. Initialize project: `wren context init`\n5. Build manifest: `wren context build`\n6. Index: `wren memory index`\n7. Verify: `wren --sql \"SELECT * FROM <model> LIMIT 5\"`\n\n---\n\n## Workflow 4: After MDL changes\n\nWhen model YAML files are updated, rebuild and re-index:\n\n```bash\n# 1. Validate changes\nwren context validate\n\n# 2. Rebuild manifest\nwren context build\n\n# 3. Re-index schema memory\nwren memory index\n\n# 4. Verify\nwren --sql \"SELECT * FROM <changed_model> LIMIT 1\"\n```\n\n---\n\n## Command decision tree\n\n```text\nGet data back           → wren --sql \"...\"\nAggregation across dims → wren cube query --cube <name> --measures <m> (if cube defined)\nRanked / top-N          → wren cube query ... --order-by \"<measure>:desc\" --limit N\nSee translated SQL only → wren dry-plan --sql \"...\" (accepts -d <datasource> if no active profile)\nValidate against DB     → wren dry-run --sql \"...\"\nSchema context          → wren memory fetch -q \"...\"\nFilter by type/model    → wren memory fetch -q \"...\" --type T --model M --threshold 0\nStore confirmed query   → wren memory store --nl \"...\" --sql \"...\"\nFew-shot examples       → wren memory recall -q \"...\"\nIndex stats             → wren memory status\nRe-index after MDL change → wren memory index\nShow project context    → wren context show\nRebuild manifest        → wren context build\nCheck profile           → wren profile debug\nSwitch profile          → wren profile switch <name>\n```\n\n---\n\n## Cube Query Workflow\n\nWhen the user asks an aggregation question (e.g., \"total revenue by month\",\n\"top customers\"), check if the MDL defines cubes before writing raw SQL.\n\n### Step 1: Discover cubes\n\n```bash\nwren cube list\n```\n\nIf cubes exist and cover the user's question, prefer cube query over raw SQL.\nLower error rate, especially for small / local models — agents don't have to\nhand-write GROUP BY / DATE_TRUNC.\n\n### Step 2: Inspect cube structure\n\n```bash\nwren cube describe <cube_name>\n```\n\nShows the cube's baseObject, measures (with expressions), dimensions,\ntime dimensions, and hierarchies.\n\n### Step 3: Match user's question to cube measures + dimensions\n\n| User phrase | Maps to |\n|---|---|\n| \"total revenue\" | `--measures total` |\n| \"by month\" | `--time-dimension \"order_date:month\"` |\n| \"in 2024\" | `--time-dimension \"order_date:month:2024-01-01,2025-01-01\"` |\n| \"for completed orders\" | `--filter \"status:eq:completed\"` |\n| \"top N customers\" | `--dimensions customer --order-by \"total:desc\" --limit N` |\n| \"worst / lowest / bottom N\" | `--order-by \"<measure>:asc\" --limit N` |\n| \"sorted by name\" | `--order-by \"<dimension>:asc\"` |\n\n### Step 4: Execute via CLI flags OR JSON input\n\nCLI flags:\n\n```bash\nwren cube query \\\n  --cube revenue \\\n  --measures total,order_count \\\n  --time-dimension \"order_date:month:2024-01-01,2025-01-01\" \\\n  --filter \"status:eq:completed\" \\\n  --order-by \"total:desc\" \\\n  --limit 100\n```\n\n**`--limit` without `--order-by` is not a top-N.** It returns an arbitrary N rows\n(or the earliest N by the time dimension when one is present). Whenever the user\nasks for \"top\", \"best\", \"worst\", or \"largest\", add `--order-by`.\n\nOrdering rules: the member must be one this query already selects (a measure,\ndimension, or time dimension); direction is lowercase `asc` or `desc`; each member\nmay appear once. Omitting `--order-by` keeps the previous default ordering.\n\nName the member as the cube declares it. A time dimension appears in the generated\nSQL as `<name>__<granularity>`, but `--order-by` wants the declared name:\n`--time-dimension \"order_date:month\" --order-by \"order_date:desc\"`.\n\nJSON input (good for agent-generated structured queries):\n\n```bash\necho '{\"cube\":\"revenue\",\"measures\":[\"total\"]}' | wren cube query --from -\n```\n\nAdd `--sql-only` to print the generated SQL without executing — useful for\nverification before paying for execution on a remote warehouse.\n\n### Step 5: Error recovery\n\n| Error | Action |\n|---|---|\n| `Unknown measure 'X'` | `wren cube describe <cube>` for available measures |\n| `Unknown dimension 'X'` | `wren cube describe <cube>` for available dimensions |\n| `Cube 'X' not found` | `wren cube list` |\n| `Cannot order by member 'X': member is not selected by the query` | Add `X` to `--measures` / `--dimensions`, or order by a member already selected |\n| `Cannot order by member 'X' more than once` | Drop the duplicate — each member may appear once in `--order-by` |\n| `` unknown variant `DESC`, expected `asc` or `desc` `` | Directions are lowercase: use `desc`, not `DESC` |\n| `Circular dependency detected` | Derived measure references itself — inspect the cube YAML |\n\n### When NOT to use cube query\n\nFall back to `wren --sql` when:\n\n- Custom JOINs across multiple models\n- Window functions, CTEs, or subqueries\n- Queries with no aggregation\n- No cubes defined in the MDL\n\n---\n\n## Aggregation decision tree\n\n```text\nUser question → Is it an aggregation question?\n                (SUM, COUNT, AVG, GROUP BY, \"by month\", \"per customer\", ...)\n  ├── Yes → Are cubes defined? (`wren cube list` once at start of session)\n  │         ├── Yes → Does a cube cover the question? (`wren cube describe`)\n  │         │         ├── Yes → Use `wren cube query` (preferred — lower error rate)\n  │         │         └── No  → Write raw SQL with `wren --sql`\n  │         └── No  → Write raw SQL with `wren --sql`\n  └── No  → Write raw SQL with `wren --sql` (look for memory recall first)\n```\n\n---\n\n## Things to avoid\n\n- Do not guess model or column names — check context first\n- Do not store failed queries or queries the user said are wrong\n- Do not skip storing successful queries with a clear NL question — default is to store\n- Do not re-index before every query — once per MDL change\n- Do not pass passwords via `--connection-info` if shell history is shared — use profiles (`wren profile add`) or `--connection-file`\n\n<!-- File: evals/spodbtify_ab/README.md (Tokens: ~868 | Category: anthropic-skill) -->\n# Spodbtify A/B Eval\n\nThis eval compares two workflows for turning the same DuckDB database into a\nqueryable Wren semantic layer, then scoring how well an AI coding agent answers\n20 analytical Spotify playlist questions.\n\nThe eval is intentionally agent-agnostic. It is not tied to Claude Code: use\nthe same spec with Claude, Codex, Cursor, an MCP client, or any runner that can\nconsume a prompt file and write an answer file.\n\n## Dataset\n\nExpected local dataset:\n\n```bash\nexport SPODBTIFY_DUCKDB_PATH=/path/to/spodbtify.duckdb\n```\n\nExpected dbt project artifacts:\n\n```bash\nexport SPODBTIFY_DBT_PROJECT_DIR=/path/to/spodbtify-dbt-project\n\n$SPODBTIFY_DBT_PROJECT_DIR/target/manifest.json\n$SPODBTIFY_DBT_PROJECT_DIR/target/catalog.json\n$SPODBTIFY_DBT_PROJECT_DIR/target/run_results.json\n$SPODBTIFY_DBT_PROJECT_DIR/target/compiled/\n```\n\nThe dataset is intentionally not checked into this repository.\n\n## Files\n\n- `spodbtify_ab_eval.json` is the canonical eval spec: workflows, controls,\n  table inventory, scoring rubric, questions, and result schema.\n- `agent_output.schema.json` is an optional JSON Schema for agents that support\n  structured output.\n- `run_eval.py` validates the spec, generates prompts, runs arbitrary agent\n  command templates, and summarizes score files.\n\n## Quick Validation\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py validate\n```\n\n## Generate Prompts\n\nPrint one prompt:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py prompt \\\n  --agent codex \\\n  --workflow dbt_integrated \\\n  --question 1\n```\n\nMaterialize prompt files for all questions without running an agent:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py run-agent \\\n  --agent codex \\\n  --workflow both \\\n  --dry-run\n```\n\n## Run With Any Agent CLI\n\n`run-agent` accepts a shell command template. These placeholders are available:\n\n- `{prompt_file}`\n- `{output_file}`\n- `{agent}`\n- `{workflow}`\n- `{question_id}`\n- `{schema_file}`\n\nExample shape:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py run-agent \\\n  --agent codex \\\n  --workflow schema_only \\\n  --command 'codex exec --sandbox danger-full-access --output-schema {schema_file} -o {output_file} - < {prompt_file}'\n```\n\nUse the equivalent command for Claude or another agent:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py run-agent \\\n  --agent claude \\\n  --workflow dbt_integrated \\\n  --command 'claude -p \"$(cat {prompt_file})\" > {output_file}'\n```\n\nExact CLI flags vary by tool and installation. The eval contract is the prompt\nfile in, answer file out. Keep one fresh session per workflow and do not share\nanswers or memory between workflows.\n\n## Scoring Contract\n\nAgents should return JSON with:\n\n```json\n{\n  \"question_id\": 1,\n  \"workflow\": \"dbt_integrated\",\n  \"agent\": \"codex\",\n  \"selected_tables\": [\"Top_Artists\", \"Artist_Collaborators\"],\n  \"sql\": \"SELECT ...\",\n  \"answer\": \"The analytical answer...\",\n  \"notes\": \"Optional caveats\"\n}\n```\n\nAfter a human or external grader assigns the three 0/1/2 scores for each\nquestion, store them in the score schema shown by:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py new-score-template \\\n  --agent codex \\\n  --workflow dbt_integrated \\\n  --output /tmp/codex-dbt-score.json\n```\n\nThen summarize:\n\n```bash\npython3 evals/spodbtify_ab/run_eval.py score --scores /tmp/codex-dbt-score.json\n```\n\nDo not commit agent answer files, run directories, or scored result files.\n\n<!-- File: skills/AUTHORING.md (Tokens: ~1032 | Category: anthropic-skill) -->\n# Skill Authoring Guide\n\n> **Where to author new skill content:**\n> **NOT here under `skills/`.** New skill guides ship as Python package data\n> in [`core/wren/src/wren/skills_content/<name>/`](../core/wren/src/wren/skills_content/),\n> served at runtime by `wren skills get <name>`. That keeps the content\n> version-aligned with the installed `wren` CLI — no skill cache, no\n> `versions.json` drift hack.\n>\n> This `skills/` tree only contains the **distribution stubs** that AI clients\n> install (one new discovery stub `wren/`, plus five deprecated redirect\n> stubs kept for one release).\n\nThe skill content format below still follows the\n[Agent Skills](https://agentskills.io/) open spec — the only thing that\nchanged is where the files live and how they reach the agent.\n\n---\n\n## Directory layout (under `core/wren/src/wren/skills_content/`)\n\n```text\n<name>/\n├── SKILL.md              # Required — frontmatter + workflow instructions\n├── references/           # Optional — reference docs, served by `wren skills get <name> --full`\n│   ├── some-topic.md\n│   └── another-topic.md\n└── scripts/              # Optional — bundled scripts, served by `wren skills get <name> --script <s>`\n```\n\n---\n\n## Frontmatter\n\n```yaml\n---\nname: <name>              # matches the parent directory; lowercase, hyphens\ndescription: \"What this skill does and when to trigger it. Include trigger\n  keywords so an AI client can match it to user intent.\"\nlicense: Apache-2.0\nmetadata:\n  author: wrenai\n---\n```\n\n**Drop the `version:` field.** Content version = the installed wrenai\nversion, since the SKILL.md ships inside the wheel.\n\n---\n\n## Progressive disclosure\n\n| Tier | Content | Loaded when |\n|------|---------|-------------|\n| 1 — Discovery stub | `skills/wren/SKILL.md` frontmatter description (~few hundred tokens) | At every AI-client session start |\n| 2 — Main guide | `wren skills get <name>` | The agent runs the command when the user's task matches |\n| 3 — References | `wren skills get <name> --full` | The agent opts in when it needs depth |\n| 3 — Scripts | `wren skills get <name> --script <s>` | Same |\n\nKeep `SKILL.md` under ~500 lines. Move reference-only content into `references/`.\n\n---\n\n## What goes where\n\n### Keep in `SKILL.md`\n- Step-by-step workflow the agent follows\n- Decision criteria and branching logic\n- Short commands the agent needs immediately\n- Quick reference tables (file paths, phase mappings, etc.)\n\n### Move to `references/`\n- Output templates (report formats, plan file formats)\n- Per-case investigation details\n- Large lookup tables\n- Anything that would push `SKILL.md` past ~500 lines\n\nCross-link inside the same skill by reference name (delivered via `--full`):\n```markdown\nFor the CTE rewrite pipeline, see the `wren-sql` reference (run\n`wren skills get usage --full`).\n```\n\nCross-link to another skill via its CLI command:\n```markdown\nFor day-to-day querying after setup, run `wren skills get usage`.\n```\n\nCross-link to a general doc:\n```markdown\nFor troubleshooting, see [`connect.md`](https://github.com/Canner/WrenAI/blob/main/docs/core/guides/connect.md).\n```\n\n---\n\n## Naming\n\n| Item | Convention | Example |\n|------|------------|---------|\n| Skill directory | `kebab-case` | `generate-mdl/` |\n| `name` field | same as directory | `generate-mdl` |\n| Reference files | descriptive `kebab-case` | `memory.md`, `wren-sql.md` |\n| Script files | descriptive `kebab-case` | `introspect_dlt.py` |\n\n---\n\n## Registering a new skill\n\n1. Create the directory under `core/wren/src/wren/skills_content/<name>/` with `SKILL.md` (and optional `references/`, `scripts/`).\n2. Author the content following the format above.\n3. Add a test to `core/wren/tests/unit/test_skills_cli.py` verifying `wren skills get <name>` returns the guide and `--full` inlines its references (if any).\n4. Done — there is no separate `versions.json` / `index.json` to update; the package-data ships with the wheel and `wren skills list` enumerates it automatically.\n\nNo `bash skills/check-versions.sh` step — version drift is impossible by\nconstruction (content travels with the binary).\n\n<!-- File: skills/index.json (Tokens: ~315 | Category: anthropic-skill) -->\n{\n  \"name\": \"wrenai\",\n  \"description\": \"Discovery stub for the Wren AI agent skill bundle. The actual workflow guides and prompt helpers live inside the wren CLI (`pip install wrenai`) and are served by `wren skills get` / `wren ask`.\",\n  \"homepage\": \"https://wren.ai\",\n  \"repository\": \"https://github.com/Canner/WrenAI\",\n  \"license\": \"Apache-2.0\",\n  \"skills\": [\n    {\n      \"name\": \"wren\",\n      \"description\": \"Wren CLI discovery stub. The actual workflow guides live inside the wren CLI; this stub teaches an AI agent to call `wren skills list`, `wren skills get <name>`, and `wren ask <prompt> --guided|--direct`. Use whenever the user asks a data question, wants to install / set up Wren Engine, connect a new database, load SaaS data via dlt, generate or regenerate an MDL project, enrich a project with business context, or build & deploy a shareable GenBI web app / dashboard from a project's semantic layer (Vercel / Cloudflare).\",\n      \"tags\": [\n        \"wren\",\n        \"discovery-stub\",\n        \"cli\",\n        \"sql\",\n        \"mdl\",\n        \"semantic-layer\",\n        \"genbi\"\n      ],\n      \"repository\": \"https://github.com/Canner/WrenAI/tree/main/skills/wren\"\n    }\n  ]\n}\n\n<!-- File: skills/README.md (Tokens: ~435 | Category: anthropic-skill) -->\n# Wren Engine — Agent Skill Distribution\n\nThe actual skill content (workflow guides, reference docs, prompt helpers)\n**lives inside the `wren` CLI**. This directory ships a single discovery stub\nthat an AI client installs once; the stub then tells the agent to fetch\neverything else from the CLI at runtime (so content always matches the\ninstalled wrenai version).\n\nSee [`SKILLS.md`](SKILLS.md) for the full design and command surface.\n\n## Install\n\n### The CLI itself (where all skill content lives)\n\n```bash\npip install wrenai\n```\n\n### The discovery stub (so an AI client knows the CLI exists)\n\n#### Option 1 — Claude Code plugin\n\n```text\n/plugin marketplace add Canner/WrenAI --path skills\n/plugin install wren@wren\n```\n\n#### Option 2 — `npx skills`\n\n```bash\nnpx skills add Canner/WrenAI\n```\n\nThe installer auto-detects your AI client. To target a specific one, add\n`--agent <name>` (e.g. `claude-code`, `cursor`, `windsurf`, `cline`).\n\n#### Option 3 — local install script\n\n```bash\nbash skills/install.sh                 # install the discovery stub\nbash skills/install.sh --force         # overwrite existing\n```\n\n## What the agent does with the stub\n\nOnce installed, the agent reads `wren/SKILL.md` and learns to call:\n\n```bash\nwren skills list                        # discover workflow guides\nwren skills get onboarding              # fetch a guide (one of 5 names)\nwren docs connection-info <ds>          # connection fields for a data source\nwren ask \"<question>\" --guided|--direct # wrap a prompt for an agent\n```\n\n## Requirements\n\n- `wren` CLI installed (`pip install wrenai` or `pip install \"wrenai[<extras>]\"`)\n- A database connection (configured via `wren profile add`)\n- An AI client that supports skills (Claude Code, Cursor, Cline, etc.)\n\n<!-- File: skills/wren/SKILL.md (Tokens: ~814 | Category: anthropic-skill) -->\n---\nname: wren\ndescription: \"Wren CLI for AI agents — a semantic SQL layer over 22+ databases (Postgres, MySQL, BigQuery, Snowflake, Spark, …). The actual workflow guides live inside the `wren` CLI itself; this is just a discovery stub. Use whenever the user asks a data question (how many, show me, top N, compare, trend, breakdown, metric, revenue, customers, orders), wants to install / set up Wren Engine, connect a new database, connect SaaS data via dlt (HubSpot, Stripe, Salesforce, GitHub, Slack), generate or regenerate an MDL project from a database schema, enrich a project with business context (enum meanings, units, cubes like ARR / DAU / churn), or turn a project's context layer into a shareable GenBI web app / dashboard and deploy it to Vercel or Cloudflare. Triggers: 'install wren', 'set up wren engine', 'connect database to wren', 'connect SaaS to wren', 'load hubspot / stripe / salesforce data', 'generate mdl', 'scaffold wren project', 'enrich wren context', 'augment my project', 'add cubes', 'build a dashboard', 'make a shareable analytics app', 'deploy my context layer as a web app', 'genbi app', 'wren onboarding', 'wren usage', 'wren generate mdl', 'wren dlt connector', 'wren enrich context', 'wren genbi'.\"\nlicense: Apache-2.0\nallowed-tools: Bash(wren:*)\n---\n\n# Wren CLI\n\nThis is a discovery stub. The actual workflow guides and prompt helpers\nlive inside the `wren` CLI itself, so they always match the installed\nwrenai version (no skill cache, no version drift).\n\nInstall: `pip install wrenai`.\n\n## Workflow guides\n\n```bash\nwren skills list                        # all available workflow guides\nwren skills get onboarding              # set up Wren end-to-end\nwren skills get usage                   # day-to-day querying\nwren skills get generate-mdl            # generate MDL from a database schema\nwren skills get dlt-connector           # connect SaaS sources via dlt\nwren skills get enrich-context          # add business context (units, enums, cubes)\nwren skills get genbi                   # build & deploy a shareable GenBI web app\n# add --full to include the skill's reference docs\n# add --script <name> to fetch a bundled script (e.g. dlt-connector / introspect_dlt)\n```\n\n## Reference docs\n\nFull reference docs live on the web: <https://github.com/Canner/WrenAI/tree/main/docs/core>\n\n```bash\nwren docs connection-info <ds>          # required + optional connection fields for a data source\n```\n\n## Prompt enhancement (wraps a user question for an agent)\n\n```bash\nwren ask \"<question>\" --guided          # for weaker LLMs (strict task flow)\nwren ask \"<question>\" --direct          # for stronger LLMs (minimal wrapping)\n```\n\n## Day-to-day data commands (not a sub-app — top-level)\n\n```bash\nwren --sql '...'                        # execute SQL through the MDL layer\nwren query --sql '...'                  # same, explicit\nwren dry-plan --sql '...'               # transpile only, no DB hit\nwren context show / build / validate    # project / MDL lifecycle\nwren profile add / list / switch        # named connection profiles\nwren memory index / recall / store      # semantic memory (needs `[memory]` extra)\n```\n\nRun `wren --help` for the full surface; load the matching `wren skills get\n<name>` guide before driving any multi-step workflow.\n\n<!-- File: skills/.claude-plugin/marketplace.json (Tokens: ~261 | Category: marketplace) -->\n{\n  \"name\": \"wren\",\n  \"owner\": {\n    \"name\": \"Canner\",\n    \"email\": \"dev@cannerdata.com\"\n  },\n  \"metadata\": {\n    \"description\": \"Wren Engine — discovery stub for AI agents. Actual workflow guides and prompt helpers ship inside the wren CLI (pip install wrenai) and are served by `wren skills get` / `wren ask`.\"\n  },\n  \"plugins\": [\n    {\n      \"name\": \"wren\",\n      \"source\": \".\",\n      \"description\": \"Wren CLI for AI agents — semantic SQL over 22+ databases. Installs a single discovery stub that points the agent at the wren CLI for all workflow content.\",\n      \"version\": \"1.0.0\",\n      \"keywords\": [\n        \"wren\",\n        \"sql\",\n        \"mdl\",\n        \"cli\",\n        \"semantic-layer\",\n        \"postgres\",\n        \"bigquery\",\n        \"snowflake\",\n        \"mysql\",\n        \"clickhouse\",\n        \"trino\",\n        \"mssql\",\n        \"databricks\",\n        \"redshift\",\n        \"spark\",\n        \"athena\",\n        \"oracle\",\n        \"duckdb\"\n      ]\n    }\n  ]\n}\n\n<!-- File: skills/.claude-plugin/plugin.json (Tokens: ~208 | Category: plugin-manifest) -->\n{\n  \"name\": \"wren\",\n  \"description\": \"AI agent skills for Wren Engine CLI — semantic SQL layer for 22+ data sources.\",\n  \"version\": \"1.0.0\",\n  \"author\": {\n    \"name\": \"Canner\",\n    \"url\": \"https://www.getwren.ai/\"\n  },\n  \"homepage\": \"https://www.getwren.ai/\",\n  \"repository\": \"https://github.com/Canner/WrenAI\",\n  \"license\": \"Apache-2.0\",\n  \"keywords\": [\n    \"wren\",\n    \"sql\",\n    \"mdl\",\n    \"cli\",\n    \"semantic-layer\",\n    \"data-source\",\n    \"postgres\",\n    \"bigquery\",\n    \"snowflake\",\n    \"mysql\",\n    \"clickhouse\",\n    \"trino\",\n    \"mssql\",\n    \"databricks\",\n    \"redshift\",\n    \"spark\",\n    \"athena\",\n    \"oracle\",\n    \"duckdb\"\n  ]\n}\n</yakaai_skills>"}