From e27ca7004ff4f9b70792bdc668bb092db2dab779 Mon Sep 17 00:00:00 2001 From: Eddie A Tejeda <669988+eddietejeda@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:35:57 -0700 Subject: [PATCH 1/3] refactor(skills): bundle search/analytics/geospatial under hotdata + fix CLI drift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only the top-level `hotdata` skill should appear in the agent skill list (autocomplete). The specialized guides — search, analytics, geospatial — are now bundled inside it under `skills/hotdata/subskills//` and loaded on demand via progressive disclosure, instead of installing as three separate top-level skills. Nested SKILL.md files are not discovered by Claude Code (discovery is first-level only), so only `hotdata` registers while the three sub-skills ride along inside the `hotdata` directory in the release tarball. - skill.rs: `SKILL_NAMES = ["hotdata"]`; add `RETIRED_SKILL_NAMES` + cleanup so upgraders' stale top-level `hotdata-search` / `-analytics` / `-geospatial` entries are removed from the store, `~/.agents`, and each agent root on install/update (global and project paths). - hotdata/SKILL.md: replace "Bundled sub-skills / install all" with a "Sub-skills (loaded on demand)" section that Reads the nested files by path with trigger keywords. - Cargo.toml: point pre-release version bumps at the nested sub-skill paths. Also fix accumulated CLI drift in the skill docs (verified against the v0.21.0 CLI): - `connections` is not a command — sources are `ingest` datasources; replace `hotdata connections list/create/refresh` with `ingest list-datasources` / `new-datasource` / `show-datasource`. - `tables list` has no `--connection-id`; `indexes list` has no `-c` / `--connection-id` — use `--schema` / `--table` filters. - add `managed_load` to `jobs --job-type`; add `--result-id` to `databases load` / `databases tables load`; document `query status` exit codes (0/1/2/3). - repoint all cross-skill relative links to the new nested locations. --- Cargo.toml | 6 +- skills/hotdata/SKILL.md | 35 +++++------ skills/hotdata/references/MODEL_BUILD.md | 24 ++++---- skills/hotdata/references/WORKFLOWS.md | 36 +++++------ .../subskills/analytics}/SKILL.md | 4 +- .../analytics}/references/WORKFLOWS.md | 4 +- .../subskills/geospatial}/SKILL.md | 6 +- .../geospatial}/references/functions.md | 0 .../subskills/search}/SKILL.md | 0 .../subskills/search}/references/INDEXES.md | 6 +- src/commands/skill.rs | 60 +++++++++++++++++-- 11 files changed, 115 insertions(+), 66 deletions(-) rename skills/{hotdata-analytics => hotdata/subskills/analytics}/SKILL.md (96%) rename skills/{hotdata-analytics => hotdata/subskills/analytics}/references/WORKFLOWS.md (95%) rename skills/{hotdata-geospatial => hotdata/subskills/geospatial}/SKILL.md (94%) rename skills/{hotdata-geospatial => hotdata/subskills/geospatial}/references/functions.md (100%) rename skills/{hotdata-search => hotdata/subskills/search}/SKILL.md (100%) rename skills/{hotdata-search => hotdata/subskills/search}/references/INDEXES.md (84%) diff --git a/Cargo.toml b/Cargo.toml index 4ef90ef..d24b7ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,9 +76,9 @@ pre-release-hook = ["git-cliff", "--unreleased", "--tag", "v{{version}}", "--pre publish = false pre-release-replacements = [ { file = "skills/hotdata/SKILL.md", search = "^version: .+", replace = "version: {{version}}", exactly = 1 }, - { file = "skills/hotdata-search/SKILL.md", search = "^version: .+", replace = "version: {{version}}", exactly = 1 }, - { file = "skills/hotdata-analytics/SKILL.md", search = "^version: .+", replace = "version: {{version}}", exactly = 1 }, - { file = "skills/hotdata-geospatial/SKILL.md", search = "^version: .+", replace = "version: {{version}}", exactly = 1 }, + { file = "skills/hotdata/subskills/search/SKILL.md", search = "^version: .+", replace = "version: {{version}}", exactly = 1 }, + { file = "skills/hotdata/subskills/analytics/SKILL.md", search = "^version: .+", replace = "version: {{version}}", exactly = 1 }, + { file = "skills/hotdata/subskills/geospatial/SKILL.md", search = "^version: .+", replace = "version: {{version}}", exactly = 1 }, # README.md no longer carries a hardcoded version badge — it tracks GitHub releases. ] diff --git a/skills/hotdata/SKILL.md b/skills/hotdata/SKILL.md index 767a803..ee8b70f 100644 --- a/skills/hotdata/SKILL.md +++ b/skills/hotdata/SKILL.md @@ -1,6 +1,6 @@ --- name: hotdata -description: Use this skill when the user wants to run core hotdata CLI commands — auth, workspaces, managed databases, tables, basic SQL query, database context (context:DATAMODEL), jobs, ingest (pull external data), and skill install. Activate for "run hotdata", "list workspaces", "list databases", "managed database", "load parquet", "list tables", "show table columns", "execute a query", "database context", "context:DATAMODEL", "ingest", "datasource", "import data from", "connect a data source", "connector", "pull data from postgres/mysql/an API/S3 buckets/Iceberg", or general Hotdata CLI usage. For full-text/vector search and retrieval indexes use hotdata-search; for OLAP analytics, query history, stored results, and Chain materializations use hotdata-analytics; for geospatial/GIS use hotdata-geospatial. +description: Use this skill when the user wants to run core hotdata CLI commands — auth, workspaces, managed databases, tables, basic SQL query, database context (context:DATAMODEL), jobs, ingest (pull external data), and skill install. Activate for "run hotdata", "list workspaces", "list databases", "managed database", "load parquet", "list tables", "show table columns", "execute a query", "database context", "context:DATAMODEL", "ingest", "datasource", "import data from", "connect a data source", "connector", "pull data from postgres/mysql/an API/S3 buckets/Iceberg", or general Hotdata CLI usage. This skill bundles three specialized guides under subskills/, loaded on demand: read subskills/search/SKILL.md for full-text/vector search and retrieval indexes, subskills/analytics/SKILL.md for OLAP analytics, query history, stored results, and Chain materializations, and subskills/geospatial/SKILL.md for geospatial/GIS. version: 0.21.0 --- @@ -14,16 +14,17 @@ hotdata [args] Or if installed on PATH: `hotdata [args]` -## Bundled sub-skills +## Sub-skills (loaded on demand) -Install all skills with **`hotdata skills install`**. Load specialized skills only when the task needs them: +This is the only top-level hotdata skill. Three specialized guides ship **bundled inside it** under `subskills/` and are not separate skills — **`Read` the matching file only when the task needs it** (progressive disclosure), then follow it: -| Skill | Use for | -|-------|---------| -| **`hotdata`** (this file) | Auth, workspaces, databases, tables, basic `query`, context, jobs, ingest | -| **`hotdata-search`** | BM25, vector search, `hotdata search`, bm25/vector indexes, embedding providers | -| **`hotdata-analytics`** | OLAP SQL, aggregations, query/results history, Chain materializations, sorted indexes | -| **`hotdata-geospatial`** | PostGIS-style `ST_*`, WKB, spatial joins | +| When the task involves | Read | Covers | +|------------------------|------|--------| +| BM25 / vector search, `hotdata search`, bm25/vector indexes, embedding providers | [`subskills/search/SKILL.md`](subskills/search/SKILL.md) | Search & retrieval indexes | +| OLAP SQL, aggregations, query/results history, Chain materializations, sorted indexes | [`subskills/analytics/SKILL.md`](subskills/analytics/SKILL.md) | Analytics | +| PostGIS-style `ST_*`, WKB geometry, spatial joins, GIS | [`subskills/geospatial/SKILL.md`](subskills/geospatial/SKILL.md) | Geospatial | + +Everything else — auth, workspaces, databases, tables, basic `query`, context, jobs, ingest — is in this file. The three sub-skills are referred to below by name (**`hotdata-search`**, **`hotdata-analytics`**, **`hotdata-geospatial`**); each name means the bundled file above, loaded on demand. ## Authentication @@ -108,11 +109,11 @@ hotdata databases attach [--database ] [--alias hotdata databases detach [--database ] # Preferred: load by catalog alias (auto-declares table if needed) -hotdata databases load --catalog --table [--schema public] (--file | --url | --upload-id ) [--workspace-id ] +hotdata databases load --catalog --table
[--schema public] (--file | --url | --upload-id | --result-id ) [--workspace-id ] # Also available via tables subcommand hotdata databases tables list [--database ] [--schema ] [--workspace-id ] [--output table|json|yaml] -hotdata databases tables load
[--database ] [--schema public] (--file | --url | --upload-id ) [--workspace-id ] +hotdata databases tables load
[--database ] [--schema public] (--file | --url | --upload-id | --result-id ) [--workspace-id ] hotdata databases tables delete
[--database ] [--schema public] [--workspace-id ] ``` @@ -123,9 +124,9 @@ hotdata databases tables delete
[--database ] [--schema public] [--w - `unset` — clears the active database from config. - `` — inspect one database (returns id, catalog, name, expires_at). - `delete` — removes the managed database; clears the active-database config if it matched. -- `load` (top-level shorthand) — loads parquet into `--catalog.--schema.--table`. Accepts `--file`, `--url`, or `--upload-id`. If the table was not declared at create time, the CLI automatically deletes and recreates the database with the table declared, then retries the load. +- `load` (top-level shorthand) — loads parquet into `--catalog.--schema.--table`. Accepts `--file`, `--url`, `--upload-id`, or `--result-id` (load a saved query result by id — from `hotdata results` or a query's `[result-id: …]` footer — instead of a file; the result must belong to the target database). If the table was not declared at create time, the CLI automatically deletes and recreates the database with the table declared, then retries the load. - `tables list` — lists tables with `TABLE` (`..
`), `SYNCED`, `LAST_SYNC`. Uses active database when `--database` is omitted. -- `tables load` — uploads a local parquet file (`--file`), a remote parquet URL (`--url`), or a pre-staged upload (`--upload-id`) and publishes with **replace** mode. +- `tables load` — publishes to a managed-database table (with **replace** mode) from a local parquet file (`--file`), a remote parquet URL (`--url`), a pre-staged upload (`--upload-id`), or a saved query result (`--result-id`, must belong to the target database). - `tables delete` — drops a table from the managed database. - `run` — mints a database-scoped JWT (via `POST /v1/auth/database`) and execs `` with `HOTDATA_DATABASE_TOKEN`, `HOTDATA_DATABASE_REFRESH_TOKEN`, `HOTDATA_DATABASE`, `HOTDATA_WORKSPACE`, and `HOTDATA_API_URL` injected. Pass a database id as a group positional (`hotdata databases run ...`) or via `--database `; omit both to auto-create a scratch database using `--name` / `--schema` / `--table` / `--expires-at`. Use this to launch an agent or child process whose API access is scoped to a single database. The minted JWT carries `database`, `workspaces`, `permissions:["read","write"]`, and `source:"database_token"` — read+write within the token's workspace, with that database as the default query scope. The child `hotdata` (or any tool) picks the token up from `HOTDATA_DATABASE_TOKEN`. The session is persisted at `~/.hotdata/database_session.json` (mode `0600`); the child's exit code is propagated. - `attach` — attaches a **connection** as a queryable catalog on a managed database, so the connection's **live** tables become visible inside that database's query scope. Defaults to the active database; target another with `--database`. `--alias` sets the SQL name the catalog answers to (defaults to the connection's name). This is how you query connection tables and **join across sources** — see [Querying across connections](#querying-across-connections-attach). @@ -215,7 +216,7 @@ hotdata query status - **A query runs inside one managed database** (active database or `--database`); with none set it fails *"a database is required."* The scope sees the database's own catalog **plus any attached connection catalogs only**. To query a connection's tables or join across sources, attach the connection first — see [Querying across connections (attach)](#querying-across-connections-attach). - Use `hotdata tables list` and `hotdata tables show` for discovery — not `information_schema` via `query`. (Discovery lists every workspace table; queryability still requires the table's catalog to be in the active database's scope.) - **PostgreSQL dialect.** Quote non-lowercase columns with double quotes. -- Async runs return `query_run_id` → poll with `query status` (do not re-run the same heavy SQL). +- Async runs return `query_run_id` → poll with `query status ` (do not re-run the same heavy SQL). `query status` exit codes: `0` succeeded, `1` failed, `2` still running (poll again), `3` succeeded but the result is a truncated/incomplete preview. - **Large results are complete, not a preview.** The server returns inline rows only up to a bounded cap and persists the full set out-of-band; `hotdata query` transparently fetches the full result, so the printed rows and row count are the complete set. (If the full result can't be retrieved, the CLI prints the preview and a `warning:` to stderr.) - **Backpressure is handled.** Under heavy concurrent load the server may shed a query with HTTP 429 (`OVERLOADED`); the CLI auto-retries (honoring `Retry-After`) before surfacing an error — no manual retry needed. - **OLAP** (aggregations, history, Chain, sorted indexes): **`hotdata-analytics`** skill. @@ -227,7 +228,7 @@ hotdata jobs list [--workspace-id ] [--job-type ] [--status hotdata jobs [--workspace-id ] [--output table|json|yaml] ``` - `list` shows only active jobs (`pending`, `running`) by default. Use `--all` to see all jobs. -- `--job-type`: `data_refresh_table`, `data_refresh_connection`, `create_index`. +- `--job-type`: `data_refresh_table`, `data_refresh_connection`, `create_index`, `managed_load`. - `--status`: `pending`, `running`, `succeeded`, `partially_succeeded`, `failed`. - Use `hotdata jobs ` to inspect a specific job's status, error, and result. @@ -303,7 +304,7 @@ Workspace usage for the current billing window (or since `--since`): `query_coun ### Agent skills (`skills`) -Bundled Markdown skills (**`hotdata`**, **`hotdata-search`**, **`hotdata-analytics`**, **`hotdata-geospatial`**) ship with the CLI release tarball. +A single top-level **`hotdata`** skill ships with the CLI release tarball; the specialized guides (`search`, `analytics`, `geospatial`) are bundled **inside it** under `subskills/` and load on demand, so only `hotdata` registers as an agent skill. ``` hotdata skills install [--project] @@ -311,7 +312,7 @@ hotdata skills status hotdata skills list ``` -- **`install`** — Downloads and installs skills to **`~/.hotdata/skills/`**, then symlinks into **`~/.agents/skills`** and into **`~/.claude/skills`** / **`~/.pi/skills`** when those directories exist. **`--project`** instead copies into **`./.agents/skills/`** in the current directory (and links `./.claude` / `./.pi` when present). The CLI may auto-refresh skills after an upgrade when appropriate. +- **`install`** — Downloads and installs the skill to **`~/.hotdata/skills/hotdata`**, then symlinks it into **`~/.agents/skills`** and into **`~/.claude/skills`** / **`~/.pi/skills`** when those directories exist (the bundled sub-skills ride along inside the `hotdata` directory). **`--project`** instead copies into **`./.agents/skills/hotdata`** in the current directory (and links `./.claude` / `./.pi` when present). The CLI may auto-refresh skills after an upgrade when appropriate. - **`status`** — Reports installed vs current CLI version and where skills are linked. - **`list`** — Alias for `status`: lists installed skills, their versions, and where they are linked. diff --git a/skills/hotdata/references/MODEL_BUILD.md b/skills/hotdata/references/MODEL_BUILD.md index a007d72..9acd0d1 100644 --- a/skills/hotdata/references/MODEL_BUILD.md +++ b/skills/hotdata/references/MODEL_BUILD.md @@ -8,24 +8,24 @@ Optional **deep pass** for a single authoritative markdown document stored as ** --- -## 1. Discover connections +## 1. Discover datasources ```bash -hotdata connections list +hotdata ingest list-datasources ``` -For each connection, record `id`, `name`, and `source_type`. +For each datasource, record `id`, `name`, and `source_type`. --- ## 2. Enumerate tables and columns -If the catalog may be **stale** (recent DDL, new tables missing), run **`hotdata connections refresh `** for affected connections **before** relying on `tables list`. +A datasource's schema is discovered when it is added. If the source schema may have changed (recent DDL, new tables), re-check the currently discovered tables/columns with **`hotdata ingest show-datasource `** **before** relying on `tables list`. -**Per connection:** +**Workspace tables** (list all, narrow with filters): ```bash -hotdata tables list --connection-id +hotdata tables list --schema --table
``` **Managed databases:** @@ -37,10 +37,10 @@ hotdata databases tables list Capture schema for each managed-database table (columns, types) from the table listing. -You can also refresh after enumeration if you discover drift: +You can also re-check a datasource's discovered schema after enumeration if you suspect drift: ```bash -hotdata connections refresh +hotdata ingest show-datasource ``` --- @@ -79,20 +79,20 @@ For **small** schemas (e.g. ≤5 tables in a domain), a short **ASCII diagram** ## 5. Search and index awareness -Inventory indexes on connection tables (whole workspace or filtered): +Inventory indexes (whole workspace or filtered): ```bash hotdata indexes list [-w ] -hotdata indexes list -c [--schema ] [--table
] [-w ] +hotdata indexes list [--schema ] [--table
] [-w ] ``` Per table when you only need one: ```bash -hotdata indexes list -c --schema --table
[-w ] +hotdata indexes list --schema --table
[-w ] ``` -Managed-database indexes are included in the no-flag whole-workspace `indexes list` (shown under the internal `__db_..
` label); narrow to one with `--connection-id` (the database's `default_connection_id`) / `--schema` / `--table` as above. +Managed-database indexes are included in the no-flag whole-workspace `indexes list` (shown under the internal `__db_..
` label); narrow to one with `--schema` / `--table` as above. Note: diff --git a/skills/hotdata/references/WORKFLOWS.md b/skills/hotdata/references/WORKFLOWS.md index cf367f6..d9fdf89 100644 --- a/skills/hotdata/references/WORKFLOWS.md +++ b/skills/hotdata/references/WORKFLOWS.md @@ -6,22 +6,22 @@ ## Which skill? -Load **`hotdata`** first for auth and workspace setup. Add a sub-skill only when the task needs it. +The `hotdata` skill is always loaded first (auth and workspace setup). The three specialized guides are **bundled inside it** under `subskills/` — `Read` one only when the task needs it. | User goal | Skill | Key commands | |-----------|--------|----------------| -| Login, workspaces, connections, tables, context | **`hotdata`** | `auth`, `workspaces`, `connections`, `tables`, `context` | +| Login, workspaces, datasources, tables, context | **`hotdata`** | `auth`, `workspaces`, `ingest` (datasources), `tables`, `context` | | Load parquet files into a managed database | **`hotdata`** | `databases create` + `databases load` | -| SQL analytics, aggregations, history, Chain | **`hotdata-analytics`** | `query`, `queries`, `results` | -| BM25 / vector search, retrieval indexes | **`hotdata-search`** | `search`, `indexes create`, `embedding-providers` | -| Geospatial / PostGIS-style SQL | **`hotdata-geospatial`** | `query` with `ST_*`, WKB columns | +| SQL analytics, aggregations, history, Chain | **`hotdata-analytics`** (`subskills/analytics/SKILL.md`) | `query`, `queries`, `results` | +| BM25 / vector search, retrieval indexes | **`hotdata-search`** (`subskills/search/SKILL.md`) | `search`, `indexes create`, `embedding-providers` | +| Geospatial / PostGIS-style SQL | **`hotdata-geospatial`** (`subskills/geospatial/SKILL.md`) | `query` with `ST_*`, WKB columns | | Concept | Where documented | |--------|------------------| | **Model** | This file — [Model](#model) | | **Upload path (managed databases)** | This file — [Managed databases](#managed-databases) | -| **History / Chain** | **`hotdata-analytics`** — [WORKFLOWS.md](../../hotdata-analytics/references/WORKFLOWS.md) | -| **Search indexes** | **`hotdata-search`** — [INDEXES.md](../../hotdata-search/references/INDEXES.md) | +| **History / Chain** | **`hotdata-analytics`** — [WORKFLOWS.md](../subskills/analytics/references/WORKFLOWS.md) | +| **Search indexes** | **`hotdata-search`** — [INDEXES.md](../subskills/search/references/INDEXES.md) | | **Epic flows** | This file — [Epic flows](#epic-flows) | --- @@ -36,10 +36,10 @@ End-to-end checklists. Use the linked sections for command detail and guardrails 1. [ ] `hotdata auth login` 2. [ ] `hotdata workspaces list` → `hotdata workspaces set` if not on the right workspace -3. [ ] `hotdata connections list` — note connection ids and names -4. [ ] (Optional) `hotdata connections create …` — see **`hotdata`** skill **Create a Connection** -5. [ ] `hotdata connections refresh ` if catalog may be stale -6. [ ] `hotdata tables list` and `hotdata tables list --connection-id ` for columns +3. [ ] `hotdata ingest list-datasources` — note datasource ids and names +4. [ ] (Optional) `hotdata ingest new-datasource --service ` — add a datasource (see **`hotdata`** skill → **Ingest external data**) +5. [ ] `hotdata ingest show-datasource ` to re-check discovered tables/columns if the source schema may have changed +6. [ ] `hotdata tables list` (add `--schema`/`--table` filters to narrow) and `hotdata tables show
` for columns 7. [ ] (Optional) `hotdata context list` — if `DATAMODEL` is listed, `hotdata context show DATAMODEL`; else skip `show` 8. [ ] (Optional) Bootstrap **context:DATAMODEL** — [Model](#model), [DATA_MODEL.template.md](DATA_MODEL.template.md) @@ -55,13 +55,13 @@ End-to-end checklists. Use the linked sections for command detail and guardrails 4. [ ] Chain: `hotdata query "SELECT … FROM .public. WHERE …"` 5. [ ] Record stable chains in **context:DATAMODEL** when they should outlive the session -**Detail:** [hotdata-analytics WORKFLOWS — Chain](../../hotdata-analytics/references/WORKFLOWS.md#chain) +**Detail:** [hotdata-analytics WORKFLOWS — Chain](../subskills/analytics/references/WORKFLOWS.md#chain) ### Retrieval (index then search) **Skill:** **`hotdata-search`** (schema via **`hotdata`**) -1. [ ] `hotdata tables list --connection-id ` — pick text column (BM25) or embedding/text column (vector) +1. [ ] `hotdata tables list` (filter with `--schema`/`--table`) — pick text column (BM25) or embedding/text column (vector) 2. [ ] `hotdata indexes list` — avoid duplicate bm25/vector indexes on the same column 3. [ ] Create index: - [ ] **Managed DB:** `hotdata indexes create --catalog --table --column --type bm25|vector` @@ -72,7 +72,7 @@ End-to-end checklists. Use the linked sections for command detail and guardrails - [ ] `hotdata search "…" --table … --type bm25 --column ` (explicit) 5. [ ] (Optional) Note indexes in **context:DATAMODEL → Search & index summary** -**Detail:** [hotdata-search INDEXES.md](../../hotdata-search/references/INDEXES.md) +**Detail:** [hotdata-search INDEXES.md](../subskills/search/references/INDEXES.md) ### Cross-source query (attach a connection) @@ -163,14 +163,14 @@ Follow **[MODEL_BUILD.md](MODEL_BUILD.md)** for connector enrichment, per-table ### Refresh catalog facts -When metadata may be **stale**, run `connections refresh` before `tables list`. After **`databases tables load`**, refresh is not required for the new table—use `databases tables list` or `tables list`. +A datasource's schema is discovered when it is added (`hotdata ingest new-datasource`); inspect the current discovered tables/columns with `hotdata ingest show-datasource `. After **`databases tables load`**, no refresh is required for the new table—use `databases tables list` or `tables list`. ```bash hotdata workspaces list -hotdata connections list -hotdata connections refresh # after DDL / stale remote metadata +hotdata ingest list-datasources +hotdata ingest show-datasource # re-check discovered schema after source DDL hotdata tables list -hotdata tables list --connection-id +hotdata tables list --schema --table
# narrow the workspace-wide listing hotdata databases list ``` diff --git a/skills/hotdata-analytics/SKILL.md b/skills/hotdata/subskills/analytics/SKILL.md similarity index 96% rename from skills/hotdata-analytics/SKILL.md rename to skills/hotdata/subskills/analytics/SKILL.md index 74ad0b9..85c65a7 100644 --- a/skills/hotdata-analytics/SKILL.md +++ b/skills/hotdata/subskills/analytics/SKILL.md @@ -8,7 +8,7 @@ version: 0.21.0 **OLAP-style analytics** in Hotdata: PostgreSQL-dialect SQL, query execution, run history, stored results, **Chain** materializations, and **sorted** indexes for filters and joins. -**Prerequisites:** Authenticate, workspace, and catalog discovery via the **`hotdata`** skill (`connections`, `tables`, `databases`). +**Prerequisites:** Authenticate, workspace, and catalog discovery via the **`hotdata`** skill (`ingest` datasources, `tables`, `databases`). **Related skills:** **`hotdata-search`** (BM25, vector, retrieval indexes), **`hotdata-geospatial`** (spatial SQL). @@ -24,7 +24,7 @@ hotdata query status - **PostgreSQL dialect.** Quote mixed-case identifiers: `"CustomerName"`. - Use **`hotdata tables list`** for schema discovery — not `information_schema` via `query`. - Fully qualified names: `..
`, `..
`. -- **Query scope:** every query runs inside one managed database (active or `--database`); it sees that database's own catalog plus **attached** connection catalogs only. To query a connection table, or **join a managed table against a connection table**, attach the connection first: `hotdata databases attach ` — see **`hotdata`** skill → [Querying across connections](../hotdata/SKILL.md). No managed database set → *"a database is required."* +- **Query scope:** every query runs inside one managed database (active or `--database`); it sees that database's own catalog plus **attached** connection catalogs only. To query a connection table, or **join a managed table against a connection table**, attach the connection first: `hotdata databases attach ` — see **`hotdata`** skill → [Querying across connections](../../SKILL.md#querying-across-connections-attach). No managed database set → *"a database is required."* - Long-running queries may return `query_run_id` → poll with **`query status`** (exit `2` = still running). Do not re-run identical heavy SQL while polling. - For **workspace-wide** joins and naming, load **context:DATAMODEL** when listed (`hotdata context list` → `show DATAMODEL`) — see **`hotdata`** skill. diff --git a/skills/hotdata-analytics/references/WORKFLOWS.md b/skills/hotdata/subskills/analytics/references/WORKFLOWS.md similarity index 95% rename from skills/hotdata-analytics/references/WORKFLOWS.md rename to skills/hotdata/subskills/analytics/references/WORKFLOWS.md index eb859d0..3ecdf77 100644 --- a/skills/hotdata-analytics/references/WORKFLOWS.md +++ b/skills/hotdata/subskills/analytics/references/WORKFLOWS.md @@ -2,7 +2,7 @@ OLAP-style SQL, **History** (query runs and stored results), and **Chain** (materialized follow-ups). Requires **`hotdata`** for auth, workspaces, and catalog commands. -**Related:** **`hotdata-search`** for BM25/vector indexes and `hotdata search`; **`hotdata`** [WORKFLOWS.md](../../hotdata/references/WORKFLOWS.md) for managed databases. +**Related:** **`hotdata-search`** for BM25/vector indexes and `hotdata search`; **`hotdata`** [WORKFLOWS.md](../../../references/WORKFLOWS.md) for managed databases. --- @@ -92,4 +92,4 @@ hotdata query "SELECT * FROM chain_db.public.revenue_slice WHERE ..." - Materialize when the base scan is large and the follow-up runs many times. - Keep Chain tables focused; avoid wide `SELECT *` materializations when a narrow projection suffices. -- For managed-database uploads, see **`hotdata`** WORKFLOWS — [Managed databases](../../hotdata/references/WORKFLOWS.md#managed-databases). +- For managed-database uploads, see **`hotdata`** WORKFLOWS — [Managed databases](../../../references/WORKFLOWS.md#managed-databases). diff --git a/skills/hotdata-geospatial/SKILL.md b/skills/hotdata/subskills/geospatial/SKILL.md similarity index 94% rename from skills/hotdata-geospatial/SKILL.md rename to skills/hotdata/subskills/geospatial/SKILL.md index c59b3dd..8322376 100644 --- a/skills/hotdata-geospatial/SKILL.md +++ b/skills/hotdata/subskills/geospatial/SKILL.md @@ -20,7 +20,7 @@ hotdata query "" [--workspace-id ] [--database ] [--output table|js - **Fully qualify tables** as `..
` (or `..
` for a managed database) — every `
` placeholder below means a qualified name. - **PostgreSQL dialect:** double-quote any non-lowercase identifier (e.g. `"GeoID"`). -- Discover candidate tables/columns with **`hotdata tables list --connection-id `** (connection tables) or **`hotdata databases tables`** (tables inside a managed database) — see core skill. +- Discover candidate tables/columns with **`hotdata tables list`** (filter with `--schema`/`--table`) or **`hotdata databases tables list`** (tables inside a managed database) — see core skill. --- @@ -46,7 +46,7 @@ wkb_geometry_bbox['xmin'] -- ✓ works (wkb_geometry_bbox).xmin -- ✗ not supported ``` -Find these columns by their `Binary` / `Struct` types in `hotdata tables list --connection-id `. +Find these columns by their `Binary` / `Struct` types in `hotdata tables list` (or `hotdata tables show `). --- @@ -155,7 +155,7 @@ SELECT , ST_AsText(ST_Simplify(ST_GeomFromWKB(wkb_geometry), 0.0001)) AS ## Workflow: explore a new geospatial table -1. **Find geometry columns** — `hotdata tables list --connection-id `; look for `Binary` (WKB) / `Struct` (bbox) types. +1. **Find geometry columns** — `hotdata tables list`; look for `Binary` (WKB) / `Struct` (bbox) types. 2. **Geometry types** — run the "Geometry types in a table" pattern above. 3. **Coverage / extent** — aggregate the bbox struct: ```sql diff --git a/skills/hotdata-geospatial/references/functions.md b/skills/hotdata/subskills/geospatial/references/functions.md similarity index 100% rename from skills/hotdata-geospatial/references/functions.md rename to skills/hotdata/subskills/geospatial/references/functions.md diff --git a/skills/hotdata-search/SKILL.md b/skills/hotdata/subskills/search/SKILL.md similarity index 100% rename from skills/hotdata-search/SKILL.md rename to skills/hotdata/subskills/search/SKILL.md diff --git a/skills/hotdata-search/references/INDEXES.md b/skills/hotdata/subskills/search/references/INDEXES.md similarity index 84% rename from skills/hotdata-search/references/INDEXES.md rename to skills/hotdata/subskills/search/references/INDEXES.md index 8eff510..9866459 100644 --- a/skills/hotdata-search/references/INDEXES.md +++ b/skills/hotdata/subskills/search/references/INDEXES.md @@ -14,7 +14,7 @@ - **Columns** — confirm types: ```bash - hotdata tables list --connection-id + hotdata tables list --schema --table
``` High-cardinality **text** (`title`, `body`, …) → **bm25**. **Embedding** / float list columns → **vector** (+ `--metric`). @@ -22,10 +22,10 @@ High-cardinality **text** (`title`, `body`, …) → **bm25**. **Embedding** / f ## 2. Compare to existing indexes ```bash -hotdata indexes list [--connection-id ] [--schema ] [--table
] +hotdata indexes list [--schema ] [--table
] ``` -With no `--connection-id`, this is a whole-workspace scan that **includes managed-database indexes** (shown under the internal `__db_..
` label). Skip duplicates (same table, column, and purpose). +With no filters, this is a whole-workspace scan that **includes managed-database indexes** (shown under the internal `__db_..
` label). Skip duplicates (same table, column, and purpose). ## 3. Create indexes diff --git a/src/commands/skill.rs b/src/commands/skill.rs index 8785d89..126b237 100644 --- a/src/commands/skill.rs +++ b/src/commands/skill.rs @@ -21,12 +21,16 @@ pub enum SkillCommands { const REPO: &str = "hotdata-dev/hotdata-cli"; const PRIMARY_SKILL_NAME: &str = "hotdata"; -const SKILL_NAMES: &[&str] = &[ - "hotdata", - "hotdata-search", - "hotdata-analytics", - "hotdata-geospatial", -]; +/// Skills registered as top-level agent skills (autocompletable). Only `hotdata` +/// installs at the top level; the specialized skills (search, analytics, +/// geospatial) are bundled *inside* `hotdata/subskills/` and loaded on demand by +/// the `hotdata` skill, so they never appear as separate entries. +const SKILL_NAMES: &[&str] = &["hotdata"]; +/// Skills that were previously installed as top-level entries and are now bundled +/// under `hotdata/subskills/`. On install/update we remove any stale top-level +/// copies so upgraders stop seeing them in the agent skill list. +const RETIRED_SKILL_NAMES: &[&str] = + &["hotdata-search", "hotdata-analytics", "hotdata-geospatial"]; const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION"); /// Agent root directories to check for symlink installation. @@ -281,6 +285,7 @@ pub fn install_for_version(version: &Version) { return; } let _symlinks = ensure_symlinks(); + remove_retired_skills_global(); clear_skill_auto_update_suppression(); println!("{}", format!("Agent skills updated to v{version}.").green()); } @@ -355,6 +360,46 @@ fn ensure_symlinks() -> Vec<(String, PathBuf, Result)> { results } +/// Best-effort removal of a single skill directory or symlink at `path`. +fn remove_skill_path(path: &PathBuf) { + if path.symlink_metadata().is_err() { + return; + } + let _ = if path.is_symlink() { + fs::remove_file(path) + } else { + fs::remove_dir_all(path) + }; +} + +/// Remove stale top-level copies of skills that are now bundled under +/// `hotdata/subskills/` (see `RETIRED_SKILL_NAMES`). Cleans the store, the +/// `~/.agents` layer, and every detected home agent root. Best-effort: a +/// retired skill that isn't present is simply skipped. +fn remove_retired_skills_global() { + for name in RETIRED_SKILL_NAMES { + remove_skill_path(&skill_store_path(name)); + remove_skill_path(&agents_skill_path(name)); + for (_root, link_path) in detected_agent_skill_paths(name) { + remove_skill_path(&link_path); + } + } +} + +/// Project-scoped counterpart to `remove_retired_skills_global`: clears stale +/// retired skills from `./.agents/skills` and each detected project agent root. +fn remove_retired_skills_project(cwd: &std::path::Path, project_skills_root: &std::path::Path) { + for name in RETIRED_SKILL_NAMES { + remove_skill_path(&project_skills_root.join(name)); + for root in AGENT_ROOTS { + let root_path = cwd.join(root); + if root_path.exists() { + remove_skill_path(&root_path.join("skills").join(name)); + } + } + } +} + pub fn install_project() { clear_skill_auto_update_suppression(); let current = Version::parse(CURRENT_VERSION).expect("invalid package version"); @@ -460,6 +505,8 @@ pub fn install_project() { } } } + + remove_retired_skills_project(&cwd, &project_skills_root); } pub fn install() { @@ -523,6 +570,7 @@ pub fn install() { } let symlinks = ensure_symlinks(); + remove_retired_skills_global(); println!( "{}", From 8125cd377586589788538906489188b1f099c5b3 Mon Sep 17 00:00:00 2001 From: Eddie A Tejeda <669988+eddietejeda@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:37:08 -0700 Subject: [PATCH 2/3] style: cargo fmt --- src/commands/skill.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/commands/skill.rs b/src/commands/skill.rs index 126b237..6dab9a7 100644 --- a/src/commands/skill.rs +++ b/src/commands/skill.rs @@ -29,8 +29,7 @@ const SKILL_NAMES: &[&str] = &["hotdata"]; /// Skills that were previously installed as top-level entries and are now bundled /// under `hotdata/subskills/`. On install/update we remove any stale top-level /// copies so upgraders stop seeing them in the agent skill list. -const RETIRED_SKILL_NAMES: &[&str] = - &["hotdata-search", "hotdata-analytics", "hotdata-geospatial"]; +const RETIRED_SKILL_NAMES: &[&str] = &["hotdata-search", "hotdata-analytics", "hotdata-geospatial"]; const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION"); /// Agent root directories to check for symlink installation. From 975b5aacfa1ce79ce89188e41bf564406f04b883 Mon Sep 17 00:00:00 2001 From: Eddie A Tejeda <669988+eddietejeda@users.noreply.github.com> Date: Fri, 31 Jul 2026 17:44:28 -0700 Subject: [PATCH 3/3] fix(skills): run retired-skill cleanup on the out-of-band upgrade path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review on #244. - `maybe_auto_update_after_cli_upgrade()` (the auto-refresh path hit by Homebrew/curl/package-manager upgraders) refreshed symlinks but never called `remove_retired_skills_global()`, and its version gate means it never runs again — so stale top-level `hotdata-search`/`-analytics`/`-geospatial` entries would persist permanently. Call cleanup right after `ensure_symlinks()`. - Add a unit test for `remove_retired_skills_project` (real dir + symlink → both removed, `hotdata` survives). - Give sub-skill cross-references relative paths (e.g. `../search/SKILL.md`) so a sub-skill loaded on demand can reach its siblings without the parent's name→path mapping in context. --- skills/hotdata/subskills/analytics/SKILL.md | 2 +- .../analytics/references/WORKFLOWS.md | 2 +- skills/hotdata/subskills/geospatial/SKILL.md | 2 +- skills/hotdata/subskills/search/SKILL.md | 6 +-- src/commands/skill.rs | 39 +++++++++++++++++++ 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/skills/hotdata/subskills/analytics/SKILL.md b/skills/hotdata/subskills/analytics/SKILL.md index 85c65a7..f9bd2a3 100644 --- a/skills/hotdata/subskills/analytics/SKILL.md +++ b/skills/hotdata/subskills/analytics/SKILL.md @@ -10,7 +10,7 @@ version: 0.21.0 **Prerequisites:** Authenticate, workspace, and catalog discovery via the **`hotdata`** skill (`ingest` datasources, `tables`, `databases`). -**Related skills:** **`hotdata-search`** (BM25, vector, retrieval indexes), **`hotdata-geospatial`** (spatial SQL). +**Related sub-skills** (bundled alongside this one — `Read` on demand): **`hotdata-search`** ([`../search/SKILL.md`](../search/SKILL.md) — BM25, vector, retrieval indexes), **`hotdata-geospatial`** ([`../geospatial/SKILL.md`](../geospatial/SKILL.md) — spatial SQL). --- diff --git a/skills/hotdata/subskills/analytics/references/WORKFLOWS.md b/skills/hotdata/subskills/analytics/references/WORKFLOWS.md index 3ecdf77..1901d27 100644 --- a/skills/hotdata/subskills/analytics/references/WORKFLOWS.md +++ b/skills/hotdata/subskills/analytics/references/WORKFLOWS.md @@ -2,7 +2,7 @@ OLAP-style SQL, **History** (query runs and stored results), and **Chain** (materialized follow-ups). Requires **`hotdata`** for auth, workspaces, and catalog commands. -**Related:** **`hotdata-search`** for BM25/vector indexes and `hotdata search`; **`hotdata`** [WORKFLOWS.md](../../../references/WORKFLOWS.md) for managed databases. +**Related:** **`hotdata-search`** ([`../../search/SKILL.md`](../../search/SKILL.md)) for BM25/vector indexes and `hotdata search`; **`hotdata`** [WORKFLOWS.md](../../../references/WORKFLOWS.md) for managed databases. --- diff --git a/skills/hotdata/subskills/geospatial/SKILL.md b/skills/hotdata/subskills/geospatial/SKILL.md index 8322376..accce9c 100644 --- a/skills/hotdata/subskills/geospatial/SKILL.md +++ b/skills/hotdata/subskills/geospatial/SKILL.md @@ -8,7 +8,7 @@ version: 0.21.0 Hotdata supports a subset of PostGIS-style functions in **PostgreSQL-dialect SQL**. This skill is data-agnostic — apply it to any table with geometry columns. -**Requires the core `hotdata` skill** for auth, workspace, and table discovery. **Related:** **`hotdata-analytics`** (OLAP SQL), **`hotdata-search`** (BM25/vector). +**Requires the core `hotdata` skill** for auth, workspace, and table discovery. **Related sub-skills** (bundled alongside this one — `Read` on demand): **`hotdata-analytics`** ([`../analytics/SKILL.md`](../analytics/SKILL.md) — OLAP SQL), **`hotdata-search`** ([`../search/SKILL.md`](../search/SKILL.md) — BM25/vector). ## Running these queries diff --git a/skills/hotdata/subskills/search/SKILL.md b/skills/hotdata/subskills/search/SKILL.md index 5389984..8930859 100644 --- a/skills/hotdata/subskills/search/SKILL.md +++ b/skills/hotdata/subskills/search/SKILL.md @@ -10,7 +10,7 @@ Retrieval workloads in Hotdata: **BM25 full-text**, **vector similarity**, and t **Prerequisites:** Authenticate, set a workspace, and set an active database (`hotdata databases set `) — see the **`hotdata`** skill. Use fully qualified table names: `..
`. -**Related skills:** **`hotdata-analytics`** (OLAP SQL, query history, materialized chains), **`hotdata-geospatial`** (PostGIS-style functions). +**Related sub-skills** (bundled alongside this one — `Read` on demand): **`hotdata-analytics`** ([`../analytics/SKILL.md`](../analytics/SKILL.md) — OLAP SQL, query history, materialized chains), **`hotdata-geospatial`** ([`../geospatial/SKILL.md`](../geospatial/SKILL.md) — PostGIS-style functions). --- @@ -62,8 +62,8 @@ hotdata indexes create --catalog --schema --table
\ hotdata indexes delete --catalog --schema --table
--name ``` -- **`--type` is required** on create: `bm25` (one or more text columns, comma-separated in `--column`) or `vector` (exactly one column; often embeddings or auto-embedded text). (`sorted` is also a valid `--type`, covered in **`hotdata-analytics`**.) -- **`sorted`** indexes (range/equality for OLAP filters) are documented in **`hotdata-analytics`** — this skill focuses on retrieval types. +- **`--type` is required** on create: `bm25` (one or more text columns, comma-separated in `--column`) or `vector` (exactly one column; often embeddings or auto-embedded text). (`sorted` is also a valid `--type`, covered in **`hotdata-analytics`** — [`../analytics/SKILL.md`](../analytics/SKILL.md).) +- **`sorted`** indexes (range/equality for OLAP filters) are documented in **`hotdata-analytics`** ([`../analytics/SKILL.md`](../analytics/SKILL.md)) — this skill focuses on retrieval types. - **`--async`:** poll with `hotdata jobs ` (see **`hotdata`** skill **Jobs**). - **Auto-embedding:** `--type vector` on a **text** column generates embeddings server-side. Optional `--embedding-provider-id`; default output column `{column}_embedding` (override with `--output-column`). diff --git a/src/commands/skill.rs b/src/commands/skill.rs index 6dab9a7..1ade20f 100644 --- a/src/commands/skill.rs +++ b/src/commands/skill.rs @@ -168,6 +168,7 @@ pub fn maybe_auto_update_after_cli_upgrade() { } let _symlinks = ensure_symlinks(); + remove_retired_skills_global(); let still_needed = match read_installed_version() { Some(v) if v >= current && all_skill_stores_present() => false, @@ -719,4 +720,42 @@ mod tests { Some(Version::parse("0.1.14").unwrap()) ); } + + #[test] + fn remove_retired_skills_project_clears_stale_top_level_dirs() { + let tmp = tempfile::tempdir().unwrap(); + let cwd = tmp.path(); + let project_skills_root = cwd.join(".agents").join("skills"); + + // A retired skill present as a real dir in the `.agents` layer and as a + // symlink in the `.claude` root (exercises both branches of + // `remove_skill_path`), plus the surviving `hotdata` skill. + let agents_retired = project_skills_root.join("hotdata-search"); + fs::create_dir_all(&agents_retired).unwrap(); + let agents_hotdata = project_skills_root.join("hotdata"); + fs::create_dir_all(&agents_hotdata).unwrap(); + + let claude_skills = cwd.join(".claude").join("skills"); + fs::create_dir_all(&claude_skills).unwrap(); + let claude_retired = claude_skills.join("hotdata-search"); + #[cfg(unix)] + std::os::unix::fs::symlink(&agents_retired, &claude_retired).unwrap(); + #[cfg(not(unix))] + fs::create_dir_all(&claude_retired).unwrap(); + + remove_retired_skills_project(cwd, &project_skills_root); + + assert!( + !agents_retired.exists(), + "retired skill in .agents should be removed" + ); + assert!( + claude_retired.symlink_metadata().is_err(), + "retired skill symlink in .claude should be removed" + ); + assert!( + agents_hotdata.exists(), + "the surviving hotdata skill must be left in place" + ); + } }