diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index ec7662c..8a3dc75 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -7,7 +7,7 @@ }, "metadata": { "description": "Altimate's Claude Code plugin — delegates dbt and warehouse work to the altimate-code CLI agent.", - "version": "1.1.0" + "version": "1.1.1" }, "plugins": [ { diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6330447..911bf60 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,6 +14,8 @@ Thanks for taking the time to contribute. This plugin lives in one repo (`Altima claude plugin validate --strict ./plugins/altimate-code ``` Both must exit clean. **Validating the repo root alone is not sufficient** — the marketplace-level validator does not descend into skills, so skill-frontmatter parse errors go undetected unless you also validate the plugin path. + + If you touch the CLI resolver block in a skill or command, run `./tests/resolve-cli.sh` — it checks every copy is identical and resolves correctly in bash and zsh. 4. Open a PR against `AltimateAI/altimate-claude-plugin:main`. ## Repo layout diff --git a/plugins/altimate-code/.claude-plugin/plugin.json b/plugins/altimate-code/.claude-plugin/plugin.json index 560f79e..d74bcee 100644 --- a/plugins/altimate-code/.claude-plugin/plugin.json +++ b/plugins/altimate-code/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "altimate-code", "displayName": "Altimate Code", - "version": "1.1.0", + "version": "1.1.1", "description": "Delegates dbt and warehouse work to altimate-code, a specialized CLI agent with 100+ purpose-built data tools (SQL analysis, column-level lineage, dbt build/test/run, warehouse profiling, FinOps, connectivity to Snowflake, BigQuery, Redshift, Databricks, Postgres, MySQL, DuckDB), and to the skills, knowledge and memory of a linked Altimate workspace.", "author": { "name": "AltimateAI", diff --git a/plugins/altimate-code/CHANGELOG.md b/plugins/altimate-code/CHANGELOG.md index b072262..55a43e6 100644 --- a/plugins/altimate-code/CHANGELOG.md +++ b/plugins/altimate-code/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this plugin will be documented in this file. +## [1.1.1] — 2026-10-20 + +### Fixed +- The skills and slash commands now resolve the CLI as `altimate`, its canonical name since v0.7.1, and fall back to `altimate-code` for npm installs older than v0.7.1. Older curl and PowerShell installs expose only `altimate`, so the plugin reported "not installed" on machines where the CLI was present. Workspace, link and sign-in commands use the same lookup. +- Install guidance and recovery messages name `altimate`, which every install method provides, and list both the npm and the curl install. + ## [1.1.0] — 2026-10-19 ### Fixed diff --git a/plugins/altimate-code/README.md b/plugins/altimate-code/README.md index 09e2294..17af579 100644 --- a/plugins/altimate-code/README.md +++ b/plugins/altimate-code/README.md @@ -21,13 +21,17 @@ Or from this repository's marketplace: ## Prerequisites -The altimate-code CLI must be installed and configured on your PATH: +The altimate-code CLI must be installed and configured on your PATH. Either install method works: ```bash -npm install -g altimate-code # requires Node 20+ -altimate-code # launch the TUI, then run /connect to configure your LLM provider +npm install -g altimate-code # requires Node 20+; exposes `altimate` and `altimate-code` +curl -fsSL https://www.altimate.sh/install | bash # standalone binary; exposes `altimate` +altimate # launch the TUI, then run /connect to configure your LLM provider ``` +The plugin invokes `altimate`, the CLI's canonical name, and falls back to `altimate-code` for npm installs older than v0.7.1, so standalone installs work without an `altimate-code` alias. + + Or configure a provider via environment variable (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and 20+ others — see the [altimate-code docs](https://docs.altimate.sh)). If altimate-code is not on PATH or not configured, the plugin surfaces the failure with fix instructions rather than silently falling back to Claude's native tools. @@ -35,7 +39,7 @@ If altimate-code is not on PATH or not configured, the plugin surfaces the failu ## How it works 1. A **SessionStart hook** adds a static description of altimate-code's capabilities to the session, so Claude Code knows when a task fits. -2. When a data-engineering task fires the **skill** (or you invoke **`/altimate-code:altimate `** explicitly), Claude Code runs `altimate-code run ""` locally with its `builder` agent. +2. When a data-engineering task fires the **skill** (or you invoke **`/altimate-code:altimate `** explicitly), Claude Code runs `altimate run ""` locally with its `builder` agent. 3. altimate-code plans and executes the task using your configured LLM provider. Warehouse queries, dbt commands, and file edits run **locally under your credentials**. 4. The result is written to a local output file and surfaced back in your Claude Code conversation. @@ -43,9 +47,9 @@ If altimate-code is not on PATH or not configured, the plugin surfaces the failu An [Altimate workspace](https://help.altimate.ai/workspaces/) holds your team's shared skills, knowledge, memory and warehouse integrations. Claude Code reaches it through altimate-code: every task the plugin hands over runs in your project, so it uses the workspace the project is linked to. Nothing in Claude Code needs configuring. -1. **Sign in to Altimate** — `altimate-code auth login -p altimate-backend --method "Altimate LLM Gateway"`. It opens your browser. This also sets up the model provider altimate-code uses. You can run it from Claude Code by typing it after `!`. -2. **Link the project** — ask Claude to link the project to your workspace. It lists your workspaces with `altimate-code workspace list` and links the one you choose with `altimate-code link --workspace ""`, or creates one with `altimate-code link --create`. Moving a linked project to a different workspace also needs `--yes`. `workspace list` is in newer altimate-code releases; with an older one, tell Claude the workspace's name. In a terminal, `altimate-code link` does the same with a picker; the picker does not work after `!` in Claude Code. -3. **Check it** — `altimate-code workspace status` shows the linked workspace and its page in the Altimate web app. +1. **Sign in to Altimate** — `altimate auth login -p altimate-backend --method "Altimate LLM Gateway"` (`altimate-code` on npm installs older than v0.7.1). It opens your browser. This also sets up the model provider altimate-code uses. You can run it from Claude Code by typing it after `!`. +2. **Link the project** — ask Claude to link the project to your workspace. It lists your workspaces with `altimate workspace list` and links the one you choose with `altimate link --workspace ""`, or creates one with `altimate link --create`. Moving a linked project to a different workspace also needs `--yes`. `workspace list` is in newer altimate-code releases; with an older one, tell Claude the workspace's name. In a terminal, `altimate link` does the same with a picker; the picker does not work after `!` in Claude Code. +3. **Check it** — `altimate workspace status` shows the linked workspace and its page in the Altimate web app. 4. **Ask Claude** — questions about your team's conventions, standards or knowledge go to altimate-code, which answers from the workspace. Start a request with `/altimate-code:altimate` to be sure it does: *"/altimate-code:altimate what are our naming conventions for staging models?"* What altimate-code learns during these tasks is saved to the workspace's memory and shared with your team in the CLI, the IDE and Studio. Skills added to the workspace reach the next task. @@ -69,13 +73,13 @@ Full details: **[PRIVACY.md](../../PRIVACY.md)** · Security policy and disclosu | Symptom | Fix | |---|---| -| `command not found: altimate-code` | `npm install -g altimate-code` (Node 20+), then run `altimate-code` once to configure your provider. | -| `Unauthorized` / `No provider configured` | Run `altimate-code` to open the TUI and reconfigure your provider via `/connect`, or set the provider API key env var. | -| Task hangs for several minutes | Open `altimate-code` to check for a pending prompt in the TUI, or re-run specifying a known-good model. | +| Neither `altimate-code` nor `altimate` on PATH | `npm install -g altimate-code` (Node 20+) or `curl -fsSL https://www.altimate.sh/install \| bash`, then run `altimate` once to configure your provider. | +| `Unauthorized` / `No provider configured` | Run `altimate` (`altimate-code` on npm installs older than v0.7.1) to open the TUI and reconfigure your provider via `/connect`, or set the provider API key env var. | +| Task hangs for several minutes | Open `altimate` (`altimate-code` on npm installs older than v0.7.1) to check for a pending prompt in the TUI, or re-run specifying a known-good model. | | Empty result | The task may be too ambiguous — restate with the target table, expected columns, or time window. | -| `UNKNOWN_USER` / `Database does not exist` mid-run | The CLI connects but your warehouse credentials don't match this project — reconfigure warehouse auth in the `altimate-code` TUI or `profiles.yml`. | -| `Not signed in to Altimate` | Run `altimate-code auth login -p altimate-backend --method "Altimate LLM Gateway"`. | -| Answers ignore your workspace's skills or knowledge | Run `altimate-code workspace status` in the project. If it isn't linked, ask Claude to link it, or run `altimate-code link` in a terminal. | +| `UNKNOWN_USER` / `Database does not exist` mid-run | The CLI connects but your warehouse credentials don't match this project — reconfigure warehouse auth in the `altimate` TUI (`altimate-code` on npm installs older than v0.7.1) or `profiles.yml`. | +| `Not signed in to Altimate` | Run `altimate auth login -p altimate-backend --method "Altimate LLM Gateway"` (`altimate-code` on npm installs older than v0.7.1). | +| Answers ignore your workspace's skills or knowledge | Run `altimate workspace status` in the project. If it isn't linked, ask Claude to link it, or run `altimate link` in a terminal. | | Plugin installed but never delegates | Confirm it's enabled: `claude plugin enable altimate-code@altimate-claude-plugin`. | Still stuck? See [Support](#support). diff --git a/plugins/altimate-code/commands/altimate.md b/plugins/altimate-code/commands/altimate.md index e6c3a12..9feea86 100644 --- a/plugins/altimate-code/commands/altimate.md +++ b/plugins/altimate-code/commands/altimate.md @@ -7,12 +7,8 @@ The user explicitly invoked `/altimate-code:altimate` to delegate this task to a Workflow (follow in order, no skipping): -1. **Verify altimate-code is installed:** - ```bash - command -v altimate-code - ``` - If it returns nothing, stop and tell the user: - > altimate-code is not installed. Install with `npm install -g altimate-code` (Node 20+), then run `altimate-code` once to configure your provider/warehouse auth, then re-run `/altimate-code:altimate `. +1. **Verify altimate-code is installed.** There is no separate check: the run command in step 3 resolves the CLI first, ignoring aliases and shell functions, and exits 127 with `altimate-code CLI not found` before doing anything else. The CLI ships under two names: npm and Homebrew installs expose both `altimate` and `altimate-code`, while older curl and PowerShell installs expose only `altimate`. Either one is fine. If the run exits with `altimate-code CLI not found`, stop and tell the user: + > altimate-code is not installed. Install with `npm install -g altimate-code` (Node 20+) or `curl -fsSL https://www.altimate.sh/install | bash`, then run `altimate` once to configure your provider/warehouse auth, then re-run `/altimate-code:altimate `. 2. **Vague debug** ("X is broken") with no failure named: **stop — ask the user for the specific error before delegating.** @@ -28,9 +24,14 @@ Workflow (follow in order, no skipping): $ARGUMENTS ALTIMATE_TASK )" + for ALTIMATE_NAME in altimate altimate-code; do + ALTIMATE_BIN="$(unalias "$ALTIMATE_NAME" 2>/dev/null; unset -f "$ALTIMATE_NAME" 2>/dev/null; command -v "$ALTIMATE_NAME")" + [ -x "$ALTIMATE_BIN" ] && break || ALTIMATE_BIN= + done + [ -n "$ALTIMATE_BIN" ] || { echo "altimate-code CLI not found (tried altimate, altimate-code)" >&2; exit 127; } umask 077 OUTPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/altimate-result.XXXXXX")" - altimate-code run "$TASK" \ + "$ALTIMATE_BIN" run "$TASK" \ --agent builder \ --yolo \ --output "$OUTPUT_FILE" \ @@ -45,9 +46,14 @@ Workflow (follow in order, no skipping): $ARGUMENTS ALTIMATE_TASK )" + for ALTIMATE_NAME in altimate altimate-code; do + ALTIMATE_BIN="$(unalias "$ALTIMATE_NAME" 2>/dev/null; unset -f "$ALTIMATE_NAME" 2>/dev/null; command -v "$ALTIMATE_NAME")" + [ -x "$ALTIMATE_BIN" ] && break || ALTIMATE_BIN= + done + [ -n "$ALTIMATE_BIN" ] || { echo "altimate-code CLI not found (tried altimate, altimate-code)" >&2; exit 127; } umask 077 OUTPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/altimate-result.XXXXXX")" - altimate-code run "$TASK" \ + "$ALTIMATE_BIN" run "$TASK" \ --agent builder \ --yolo --output "$OUTPUT_FILE" --dir "$(pwd)" \ --continue \ diff --git a/plugins/altimate-code/commands/review.md b/plugins/altimate-code/commands/review.md index c74f8ec..23af9ae 100644 --- a/plugins/altimate-code/commands/review.md +++ b/plugins/altimate-code/commands/review.md @@ -17,15 +17,26 @@ Run the `altimate-code review` CLI on the current dbt project and present the si ## Workflow -1. **Verify altimate-code is on PATH.** Run `command -v altimate-code`. If nothing is returned, tell the user: `"altimate-code is not installed. Install with 'npm install -g altimate-code' (Node 20+), then run 'altimate-code' once to configure your provider and warehouse auth."` — then STOP. +1. **Verify altimate-code is on PATH.** There is no separate check: the resolver below runs first in the same Bash call as the review, before any `gh pr checkout`. If it exits 127 with `altimate-code CLI not found`, tell the user: `"altimate-code is not installed. Install with 'npm install -g altimate-code' (Node 20+) or 'curl -fsSL https://www.altimate.sh/install | bash', then run 'altimate' once to configure your provider and warehouse auth."` — then STOP. + + The CLI ships under two names: npm and Homebrew installs expose both `altimate` and `altimate-code`, while older curl and PowerShell installs expose only `altimate`. Every review command below runs through `"$ALTIMATE_BIN"`, in the same Bash call as this resolver (shell state does not persist between calls). The lookup ignores aliases and shell functions, so a user alias such as `alias altimate-code=altimate` cannot be captured as the binary path: + + ```bash + for ALTIMATE_NAME in altimate altimate-code; do + ALTIMATE_BIN="$(unalias "$ALTIMATE_NAME" 2>/dev/null; unset -f "$ALTIMATE_NAME" 2>/dev/null; command -v "$ALTIMATE_NAME")" + [ -x "$ALTIMATE_BIN" ] && break || ALTIMATE_BIN= + done + [ -n "$ALTIMATE_BIN" ] || { echo "altimate-code CLI not found (tried altimate, altimate-code)" >&2; exit 127; } + + ``` 2. **Confirm you are in a dbt project root** (`dbt_project.yml` present). If not, tell the user to `cd` there and STOP. 3. **Determine scope from `$ARGUMENTS`:** - - Empty → `altimate-code review` - - Commit SHA (matches `[0-9a-f]{7,40}`) → `altimate-code review --base $ARGUMENTS~1 --head $ARGUMENTS` - - Branch name (matches local branch via `git rev-parse`) → `altimate-code review --base $ARGUMENTS` - - PR URL or number → first `gh pr checkout `, then `altimate-code review` + - Empty → `"$ALTIMATE_BIN" review` + - Commit SHA (matches `[0-9a-f]{7,40}`) → `"$ALTIMATE_BIN" review --base $ARGUMENTS~1 --head $ARGUMENTS` + - Branch name (matches local branch via `git rev-parse`) → `"$ALTIMATE_BIN" review --base $ARGUMENTS` + - PR URL or number → `gh pr checkout && "$ALTIMATE_BIN" review`, in the same Bash call after the resolver so a missing CLI stops before the checkout 4. **Run the review command as chosen above.** Capture its stdout. @@ -54,8 +65,8 @@ Anything else (`--manifest`, `--cwd`) only if the user explicitly requested it o | Symptom | Message to user | |---|---| -| `command not found: altimate-code` | "altimate-code is not installed. Install with `npm install -g altimate-code` (Node 20+), then run `altimate-code` once to configure auth." | -| `Unauthorized: Incorrect auth token` | "altimate-code's LLM provider auth is misconfigured. Run `altimate-code` to reconfigure, or re-run with `--no-ai`." | +| `altimate-code CLI not found` (neither `altimate-code` nor `altimate` on PATH) | "altimate-code is not installed. Install with `npm install -g altimate-code` (Node 20+) or `curl -fsSL https://www.altimate.sh/install \| bash`, then run `altimate` once to configure auth." | +| `Unauthorized: Incorrect auth token` | "altimate-code's LLM provider auth is misconfigured. Run `altimate` (`altimate-code` on npm installs older than v0.7.1) to reconfigure, or re-run with `--no-ai`." | | `fatal: not a git repository` | "This directory isn't a git repo. `altimate-code review` needs git to compute the diff." | | No `dbt_project.yml` | "No dbt project detected at this path. Run from the directory with `dbt_project.yml`." | | Output mentions `DEGRADED — lint-only` | Present verbatim; do NOT silently upgrade a degraded run to a full verdict. | diff --git a/plugins/altimate-code/skills/altimate-code/SKILL.md b/plugins/altimate-code/skills/altimate-code/SKILL.md index e23b733..1537fcf 100644 --- a/plugins/altimate-code/skills/altimate-code/SKILL.md +++ b/plugins/altimate-code/skills/altimate-code/SKILL.md @@ -26,7 +26,7 @@ altimate-code is a CLI AI agent with 100+ purpose-built data engineering tools. ## You MUST follow this workflow -1. **Verify altimate-code is on PATH** with `command -v altimate-code`. If it returns nothing, jump to "Not installed" below and stop. +1. **Verify altimate-code is on PATH.** There is no separate check: the invocation below resolves the CLI first, ignoring aliases and shell functions, and exits 127 with `altimate-code CLI not found` before doing anything else. The CLI ships under two names: npm and Homebrew installs expose both `altimate` and `altimate-code`, while older curl and PowerShell installs expose only `altimate`. Either name is the same CLI. If the invocation exits with `altimate-code CLI not found`, jump to "Failure modes" below and stop. 2. **Run altimate-code with the user's task** using the invocation below. Pass the user's request through verbatim — do not paraphrase or split it. 3. **Read the output file** and present it to the user as-is. @@ -49,9 +49,14 @@ TASK="$(cat <<'ALTIMATE_TASK' ALTIMATE_TASK )" +for ALTIMATE_NAME in altimate altimate-code; do + ALTIMATE_BIN="$(unalias "$ALTIMATE_NAME" 2>/dev/null; unset -f "$ALTIMATE_NAME" 2>/dev/null; command -v "$ALTIMATE_NAME")" + [ -x "$ALTIMATE_BIN" ] && break || ALTIMATE_BIN= +done +[ -n "$ALTIMATE_BIN" ] || { echo "altimate-code CLI not found (tried altimate, altimate-code)" >&2; exit 127; } umask 077 OUTPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/altimate-result.XXXXXX")" -altimate-code run "$TASK" \ +"$ALTIMATE_BIN" run "$TASK" \ --agent builder \ --yolo \ --output "$OUTPUT_FILE" \ @@ -71,6 +76,8 @@ Then `Read "$OUTPUT_FILE"` and emit its contents to the user without re-summaris | `--dir "$(pwd)"` | Runs altimate-code in the current project so it picks up dbt project config, profiles.yml, the linked workspace, etc. | | `< /dev/null` | `altimate-code run` reads piped stdin as part of the task. The shell this runs in leaves stdin open, so without the redirect the run waits for input that never comes. | +Each Bash call starts a fresh shell, so keep the `ALTIMATE_BIN` resolver loop inside the same command as the `run` — do not resolve it in an earlier call and reuse the variable. + ### Follow-up tasks in the same project When the user makes a follow-up data task in the same project after a successful altimate-code delegation, prefer `--continue` to resume the warm session instead of starting a fresh one. Same here-doc + `mktemp` pattern: @@ -80,9 +87,14 @@ TASK="$(cat <<'ALTIMATE_TASK' ALTIMATE_TASK )" +for ALTIMATE_NAME in altimate altimate-code; do + ALTIMATE_BIN="$(unalias "$ALTIMATE_NAME" 2>/dev/null; unset -f "$ALTIMATE_NAME" 2>/dev/null; command -v "$ALTIMATE_NAME")" + [ -x "$ALTIMATE_BIN" ] && break || ALTIMATE_BIN= +done +[ -n "$ALTIMATE_BIN" ] || { echo "altimate-code CLI not found (tried altimate, altimate-code)" >&2; exit 127; } umask 077 OUTPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/altimate-result.XXXXXX")" -altimate-code run "$TASK" \ +"$ALTIMATE_BIN" run "$TASK" \ --agent builder \ --yolo \ --output "$OUTPUT_FILE" \ @@ -99,12 +111,23 @@ If the user starts a clearly unrelated workflow (different project, different sc A project can be linked to an Altimate workspace: the team's shared skills, knowledge, memory and warehouse integrations. Every `altimate-code run` in a linked project uses that workspace automatically — there is nothing to pass. That is why questions about the team's conventions, standards or knowledge belong to this skill. -When the user asks about their workspace or wants to set one up: +When the user asks about their workspace or wants to set one up, run each command below through the same resolver as the `run`, in the same Bash call: + +```bash +for ALTIMATE_NAME in altimate altimate-code; do + ALTIMATE_BIN="$(unalias "$ALTIMATE_NAME" 2>/dev/null; unset -f "$ALTIMATE_NAME" 2>/dev/null; command -v "$ALTIMATE_NAME")" + [ -x "$ALTIMATE_BIN" ] && break || ALTIMATE_BIN= +done +[ -n "$ALTIMATE_BIN" ] || { echo "altimate-code CLI not found (tried altimate, altimate-code)" >&2; exit 127; } +"$ALTIMATE_BIN" < /dev/null +``` + +Commands the user types after `!` use `altimate`; on npm installs older than v0.7.1 the user types `altimate-code` instead. -- **Check the link:** `altimate-code workspace status < /dev/null`. Exit code 0 means linked and 3 means not linked. Exit code 2 with `Not signed in to Altimate` means the user must sign in. For anything else, show the user the CLI's message. -- **Not signed in:** tell the user to run `! altimate-code auth login -p altimate-backend --method "Altimate LLM Gateway"` in this session. It opens their browser to sign in. Do not try to sign in for them. Keep the `--method` flag: without it the command waits at a menu that cannot be answered from this session. -- **Not linked:** do not suggest `! altimate-code link`: its picker needs a terminal, and this session does not give it one. Instead run `altimate-code workspace list < /dev/null`, show the user the workspaces it prints, and once they choose, run `altimate-code link --workspace "" < /dev/null`. If they want a new workspace, run `altimate-code link --create < /dev/null`, which names it after the repository. If a workspace with that name already exists, `--create` refuses and names it: offer to link to that one with `--workspace`, or, if the user confirms they want a second one, re-run with `--allow-duplicate`. If the project is already linked to a different workspace, `link` refuses; confirm the move with the user, then re-run it with `--yes`. If `workspace list` is an unknown command (an older altimate-code), ask the user for the workspace's name, or tell them to run `altimate-code link` in a separate terminal. -- **Open it in the browser:** `altimate-code workspace status` prints the workspace page's URL. +- **Check the link:** `"$ALTIMATE_BIN" workspace status < /dev/null`. Exit code 0 means linked and 3 means not linked. Exit code 2 with `Not signed in to Altimate` means the user must sign in. For anything else, show the user the CLI's message. +- **Not signed in:** tell the user to run `! altimate auth login -p altimate-backend --method "Altimate LLM Gateway"` in this session. It opens their browser to sign in. Do not try to sign in for them. Keep the `--method` flag: without it the command waits at a menu that cannot be answered from this session. +- **Not linked:** do not suggest `! altimate link`: its picker needs a terminal, and this session does not give it one. Instead run `"$ALTIMATE_BIN" workspace list < /dev/null`, show the user the workspaces it prints, and once they choose, run `"$ALTIMATE_BIN" link --workspace "" < /dev/null`. If they want a new workspace, run `"$ALTIMATE_BIN" link --create < /dev/null`, which names it after the repository. If a workspace with that name already exists, `--create` refuses and names it: offer to link to that one with `--workspace`, or, if the user confirms they want a second one, re-run with `--allow-duplicate`. If the project is already linked to a different workspace, `link` refuses; confirm the move with the user, then re-run it with `--yes`. If `workspace list` is an unknown command (an older altimate-code), ask the user for the workspace's name, or tell them to run `altimate link` in a separate terminal. +- **Open it in the browser:** `"$ALTIMATE_BIN" workspace status` prints the workspace page's URL. What altimate-code learns during delegated tasks is saved to the workspace's memory and shared with the team in the CLI, the IDE and Studio. @@ -114,19 +137,19 @@ When altimate-code returns an error, **report the error to the user and STOP**. | Symptom | What to tell the user — verbatim | |---|---| -| `command not found: altimate-code` | "altimate-code is not installed. Install with `npm install -g altimate-code` (Node 20+) and run `altimate-code` once to configure auth. Then re-run your request." | -| `Unauthorized: Incorrect auth token` / `No provider configured` | "altimate-code's LLM provider auth is misconfigured. Run `altimate-code` in your terminal to open the TUI and reconfigure your provider, or sign in to Altimate with `altimate-code auth login -p altimate-backend --method 'Altimate LLM Gateway'`, then re-run your request." | -| `Not signed in to Altimate` | "altimate-code is not signed in to Altimate, so it cannot reach your workspace. Run `! altimate-code auth login -p altimate-backend --method 'Altimate LLM Gateway'`, sign in in the browser, then re-run your request." | -| `not in the workspace yet` (stderr) | Present the result as usual, then add: "altimate-code saved what it learned on this machine, but it has not reached your workspace yet. Run `! altimate-code workspace sync` to send it." | -| `Could not confirm that the memory saved here reached the workspace` (stderr) | Present the result as usual, then add: "altimate-code could not confirm that what it learned reached your workspace. Run `! altimate-code workspace status` to check." | -| Process hangs >5 min | "altimate-code is unresponsive. Try `altimate-code` to inspect the TUI for an open prompt, or re-run with `--model anthropic/claude-sonnet-4-6` to force a known-good model." | +| `altimate-code CLI not found` (neither `altimate` nor `altimate-code` on PATH) | "altimate-code is not installed. Install with `npm install -g altimate-code` (Node 20+) or `curl -fsSL https://www.altimate.sh/install \| bash`, then run `altimate` once to configure auth. Then re-run your request." | +| `Unauthorized: Incorrect auth token` / `No provider configured` | "altimate-code's LLM provider auth is misconfigured. Run `altimate` (`altimate-code` on npm installs older than v0.7.1) in your terminal to open the TUI and reconfigure your provider, or sign in to Altimate with `altimate auth login -p altimate-backend --method 'Altimate LLM Gateway'`, then re-run your request." | +| `Not signed in to Altimate` | "altimate-code is not signed in to Altimate, so it cannot reach your workspace. Run `! altimate auth login -p altimate-backend --method 'Altimate LLM Gateway'` (`altimate-code` on npm installs older than v0.7.1), sign in in the browser, then re-run your request." | +| `not in the workspace yet` (stderr) | Present the result as usual, then add: "altimate-code saved what it learned on this machine, but it has not reached your workspace yet. Run `! altimate workspace sync` (`altimate-code` on npm installs older than v0.7.1) to send it." | +| `Could not confirm that the memory saved here reached the workspace` (stderr) | Present the result as usual, then add: "altimate-code could not confirm that what it learned reached your workspace. Run `! altimate workspace status` (`altimate-code` on npm installs older than v0.7.1) to check." | +| Process hangs >5 min | "altimate-code is unresponsive. Try `altimate` (`altimate-code` on npm installs older than v0.7.1) to inspect the TUI for an open prompt, or re-run with `--model anthropic/claude-sonnet-4-6` to force a known-good model." | | Output file empty | "altimate-code returned without producing output. The task may be too ambiguous — please restate with more detail (target table, expected columns, time window)." | -| Warehouse error mid-run (`UNKNOWN_USER`, `Database does not exist`) | "altimate-code can connect but the warehouse credentials it has are wrong for this project. Configure provider/warehouse auth via `altimate-code` TUI." | +| Warehouse error mid-run (`UNKNOWN_USER`, `Database does not exist`) | "altimate-code can connect but the warehouse credentials it has are wrong for this project. Configure provider/warehouse auth via the `altimate` TUI (`altimate-code` on npm installs older than v0.7.1)." | In every row, the instruction to the user is the action — you do not retry the task with native tools. If the user fixes the underlying issue and asks again, you delegate again. ## Notes - altimate-code runs its own LLM, separate from Claude Code's. Costs and rate limits accrue to altimate-code's configured provider. -- Sessions persist in altimate-code's local store — `altimate-code session list` shows prior runs; `--continue` resumes the latest, `--session ` resumes a specific one. +- Sessions persist in altimate-code's local store — `altimate session list` (`altimate-code session list` on npm installs older than v0.7.1) shows prior runs; `--continue` resumes the latest, `--session ` resumes a specific one. - For very long tasks, the `--output` file is the source of truth — stdout buffering can drop content. diff --git a/plugins/altimate-code/skills/review/SKILL.md b/plugins/altimate-code/skills/review/SKILL.md index 5ba6adf..70bc307 100644 --- a/plugins/altimate-code/skills/review/SKILL.md +++ b/plugins/altimate-code/skills/review/SKILL.md @@ -27,7 +27,18 @@ description: >- ## You MUST follow this workflow -1. **Verify altimate-code is on PATH** with `command -v altimate-code`. If it returns nothing, jump to "Failure modes" below and stop. +1. **Verify altimate-code is on PATH.** There is no separate check: the resolver below runs first in the same Bash call as the review, before any `gh pr checkout`. If it exits 127 with `altimate-code CLI not found`, jump to "Failure modes" below and stop. + + The CLI ships under two names: npm and Homebrew installs expose both `altimate` and `altimate-code`, while older curl and PowerShell installs expose only `altimate`. Every review command below runs through `"$ALTIMATE_BIN"`, in the same Bash call as this resolver (shell state does not persist between calls). The lookup ignores aliases and shell functions, so a user alias such as `alias altimate-code=altimate` cannot be captured as the binary path: + + ```bash + for ALTIMATE_NAME in altimate altimate-code; do + ALTIMATE_BIN="$(unalias "$ALTIMATE_NAME" 2>/dev/null; unset -f "$ALTIMATE_NAME" 2>/dev/null; command -v "$ALTIMATE_NAME")" + [ -x "$ALTIMATE_BIN" ] && break || ALTIMATE_BIN= + done + [ -n "$ALTIMATE_BIN" ] || { echo "altimate-code CLI not found (tried altimate, altimate-code)" >&2; exit 127; } + + ``` 2. **Determine review scope from the user's ask** using the table below. @@ -41,10 +52,10 @@ description: >- | User says | Command to run | |---|---| -| "review my changes" / "review this branch" / "audit my dbt work" / no scope specified | `altimate-code review` (default: working tree vs merge-base with `origin/main`) | -| "review PR #123" or gives a GitHub PR URL | First `gh pr checkout 123`, then `altimate-code review` | -| "review commit `abc123`" | `altimate-code review --base abc123~1 --head abc123` | -| "compare staging to prod" / "review the diff between two branches" | `altimate-code review --base prod --head staging` | +| "review my changes" / "review this branch" / "audit my dbt work" / no scope specified | `"$ALTIMATE_BIN" review` (default: working tree vs merge-base with `origin/main`) | +| "review PR #123" or gives a GitHub PR URL | `gh pr checkout 123 && "$ALTIMATE_BIN" review`, in the same Bash call after the resolver so a missing CLI stops before the checkout | +| "review commit `abc123`" | `"$ALTIMATE_BIN" review --base abc123~1 --head abc123` | +| "compare staging to prod" / "review the diff between two branches" | `"$ALTIMATE_BIN" review --base prod --head staging` | ## Do NOT do these @@ -68,8 +79,8 @@ description: >- | Symptom | Message to the user | |---|---| -| `command not found: altimate-code` | "altimate-code is not installed. Install with `npm install -g altimate-code` (Node 20+), then run `altimate-code` once to configure your provider and warehouse auth. Then re-run the review." | -| `Unauthorized: Incorrect auth token` / `No provider configured` | "altimate-code's LLM provider auth is misconfigured. Run `altimate-code` to reconfigure — or re-run with `--no-ai` to skip the LLM lane entirely and get a deterministic-only review." | +| `altimate-code CLI not found` (neither `altimate-code` nor `altimate` on PATH) | "altimate-code is not installed. Install with `npm install -g altimate-code` (Node 20+) or `curl -fsSL https://www.altimate.sh/install \| bash`, then run `altimate` once to configure your provider and warehouse auth. Then re-run the review." | +| `Unauthorized: Incorrect auth token` / `No provider configured` | "altimate-code's LLM provider auth is misconfigured. Run `altimate` (`altimate-code` on npm installs older than v0.7.1) to reconfigure — or re-run with `--no-ai` to skip the LLM lane entirely and get a deterministic-only review." | | `fatal: not a git repository` | "This directory isn't a git repo. `altimate-code review` computes the diff via git — run from a dbt project inside a git worktree." | | No `dbt_project.yml` found | "No dbt project detected at this path. The review needs a dbt project to compile models against. Run from the directory containing `dbt_project.yml`." | | Output says `DEGRADED — lint-only` | Present that note verbatim. Do not silently upgrade a degraded run to a full verdict. | diff --git a/tests/resolve-cli.sh b/tests/resolve-cli.sh new file mode 100755 index 0000000..1041651 --- /dev/null +++ b/tests/resolve-cli.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# Checks the CLI resolver block embedded in the skills and slash commands: +# every copy must be identical, and it must pick the right binary in bash and zsh. +set -u +cd "$(dirname "$0")/.." + +# file:expected number of resolver blocks +FILES=( + plugins/altimate-code/commands/altimate.md:2 + plugins/altimate-code/commands/review.md:1 + plugins/altimate-code/skills/altimate-code/SKILL.md:3 + plugins/altimate-code/skills/review/SKILL.md:1 +) +FAILURES=0 +fail() { echo "FAIL: $*"; FAILURES=$((FAILURES + 1)); } + +# Print each resolver block (loop start through the not-found guard) on one line, dedented. +extract_blocks() { + awk '/for ALTIMATE_NAME in/ {grab=1; block=""} + grab {sub(/^ */, ""); block = block $0 "\\n"} + grab && /CLI not found/ {grab=0; print block}' "$1" +} + +ALL_BLOCKS="" +for entry in "${FILES[@]}"; do + file="${entry%:*}" + blocks="$(extract_blocks "$file")" + count="$(printf '%s' "$blocks" | grep -c .)" + [ "$count" -eq "${entry##*:}" ] || fail "$file has $count resolver blocks, expected ${entry##*:}" + ALL_BLOCKS="$ALL_BLOCKS$blocks"$'\n' +done +[ "$(printf '%s' "$ALL_BLOCKS" | grep . | sort -u | grep -c .)" -eq 1 ] || fail "resolver blocks differ between files" +RESOLVER="$(printf '%b' "$(printf '%s' "$ALL_BLOCKS" | head -n1)")" + +# A command line that calls the CLI by name skips the resolver. User-typed `!` commands in prose are fine. +for entry in "${FILES[@]}"; do + file="${entry%:*}" + bare="$(grep -nE '^[[:space:]]*altimate(-code)? (run|review|workspace|link|auth|session)' "$file")" + [ -z "$bare" ] || fail "$file calls the CLI without the resolver: $bare" +done + +STUBS="$(mktemp -d)" && [ -d "$STUBS" ] || { echo "FAIL: could not create a temp dir"; exit 1; } +trap 'rm -rf "$STUBS"' EXIT +for stub in only-altimate/altimate only-altimate-code/altimate-code both/altimate both/altimate-code "relative dir/altimate"; do + mkdir -p "$STUBS/$(dirname "$stub")" + printf '#!/bin/sh\necho "ran %s"\n' "$(basename "$stub")" > "$STUBS/$stub" + chmod +x "$STUBS/$stub" +done +mkdir -p "$STUBS/neither" "$STUBS/broken-altimate" +printf 'not a program\n' > "$STUBS/broken-altimate/altimate" + +# expect +# Runs from $STUBS without startup files and with only the stub dirs on PATH, so relative PATH +# entries resolve and neither user config nor system-installed binaries can interfere. +expect() { + local shell_cmd + case "$1" in zsh) shell_cmd=("$(command -v zsh)" -f) ;; *) shell_cmd=("$(command -v bash)" --noprofile --norc) ;; esac + out="$(cd "$STUBS" && env -u BASH_ENV -u ENV PATH="$2" "${shell_cmd[@]}" -c "$3 +$RESOLVER +\"\$ALTIMATE_BIN\"" 2>&1)" + rc=$? + [ "$rc" -eq "$4" ] && [ "$out" = "$5" ] \ + || fail "$1 / PATH=$2 / '${3:-no setup}': expected exit $4 '$5', got exit $rc '$out'" +} + +ALIAS_SETUP='shopt -s expand_aliases 2>/dev/null; alias altimate-code=altimate' +for sh in bash zsh; do + command -v "$sh" >/dev/null || { echo "skip: $sh not installed"; continue; } + expect "$sh" "$STUBS/only-altimate" '' 0 'ran altimate' + expect "$sh" "$STUBS/only-altimate-code" '' 0 'ran altimate-code' + expect "$sh" "$STUBS/both" '' 0 'ran altimate' + expect "$sh" "$STUBS/neither" '' 127 'altimate-code CLI not found (tried altimate, altimate-code)' + expect "$sh" "$STUBS/only-altimate" "$ALIAS_SETUP" 0 'ran altimate' + expect "$sh" "$STUBS/only-altimate" "$ALIAS_SETUP; altimate() { echo function; }" 0 'ran altimate' + expect "$sh" "$STUBS/only-altimate" 'altimate-code() { echo function; }' 0 'ran altimate' + expect "$sh" "$STUBS/only-altimate-code" 'shopt -s expand_aliases 2>/dev/null; alias altimate=altimate-code' 0 'ran altimate-code' + expect "$sh" "$STUBS/only-altimate-code" 'altimate-code() { echo function; }' 0 'ran altimate-code' + expect "$sh" "$STUBS/only-altimate-code" 'shopt -s expand_aliases 2>/dev/null; alias altimate-code="echo alias"' 0 'ran altimate-code' + expect "$sh" "$STUBS/neither" "$ALIAS_SETUP; altimate() { echo function; }" 127 'altimate-code CLI not found (tried altimate, altimate-code)' + expect "$sh" "$STUBS/broken-altimate:$STUBS/only-altimate-code" '' 0 'ran altimate-code' + expect "$sh" "$STUBS/broken-altimate" '' 127 'altimate-code CLI not found (tried altimate, altimate-code)' + expect "$sh" "relative dir" '' 0 'ran altimate' +done + +[ "$FAILURES" -eq 0 ] && echo "ok" || { echo "$FAILURES failure(s)"; exit 1; }