Repository navigation
Conversation
Do not merge: experiment for evaluating direct altimate-code tools in Claude Code. - Bring in the MCP registration, analyst agent and experimental listing from the plugin draft branches - Skill and analyst agent use the altimate-code MCP tools when present, otherwise `tool context` then `tool run` - SessionStart hook picks MCP or CLI mode without starting the CLI and adds routing guidance only in CLI mode - Opt-in PreToolUse guard for direct warehouse clients (`ALTIMATE_ROUTE_GUARD=1`) - Tests: `tests/data-mode.sh`, `tests/warehouse-guard.sh` Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bfc4992 to
6ff19bf
Compare
- Start the MCP server with --workspace-tools - Pass --workspace-tools on every tool run/list/schema call in CLI mode (not tool context) - Skill: workspace_context, workspace_skill and workspace_execute in both modes, receipt refresh, setup statuses, engine install on request, linking without memory upload, skill list/refresh/publish, memory save only on explicit request - Analyst agent, /altimate command and SessionStart text route workspace questions to the workspace tools - Tests: tests/workspace-tools.sh Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n both modes Do not merge: experiment. - SessionStart copies the linked workspace's skills into `.claude/skills` in MCP and CLI mode (ported from the workspace-skills draft: command blocks neutralised, signed-in tenant only, user skills never overwritten); `ALTIMATE_CLAUDE_SKILL_SYNC=0` disables it - Starts a bounded background `workspace refresh` when the snapshot is missing or older than an hour - `.mcp.json` runs `mcp serve --workspace-tools`; skill and analyst agent describe workspace context, skills and reads in both modes - Tests: `tests/session-start.sh`, `tests/test_sync_workspace_skills.py`; version 1.4.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and on demand Do not merge: experiment. - `hooks-handlers/refresh-skills.sh` refreshes the linked workspace's skills and copies them into `.claude/skills`: `--if-stale` runs in the background when the snapshot is missing or over an hour old, `--wait` refreshes now and reports the copies; one refresh per project at a time - SessionStart copies the snapshot already on disk and starts the background refresh without waiting; Claude Code loads skills added mid-session - Stop hook runs the same hourly background refresh in long sessions - `/altimate-code:refresh-skills` command for a manual refresh - Tests: `tests/refresh-skills.sh` Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…refresh Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The `altimate-code-experimental` listing, its packaging script, docs and smoke tests belong to the separate pilot PR; this branch changes only the `altimate-code` plugin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…shes, tighten docs and guard Workspace skill copies are reconciled under a per-project lock and swapped with rollback; a snapshot that cannot be read leaves copies alone; symlinked SKILL.md and tenant-less manifests are not served. Failed background refreshes retry hourly, --wait keeps another refresh's lock, the mode check avoids here-docs, the warehouse guard checks chained commands, and the analyst agent no longer reads .env. PRIVACY and README describe the workspace skill data flow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ned commands in the guard header Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
||
| if [[ "$CMD" =~ ${PREFIX}(psql|trino|clickhouse-client|clickhouse|snowsql|mysql|sqlcmd|duckdb)([[:space:]]|$) ]]; then | ||
| # Interactive or help invocations without a query are left alone. | ||
| if [[ "$CMD" =~ (^|[[:space:]])(-c|--command|-e|--execute|-q|--query|-f|--file|-x)([[:space:]=]|$) ]] || [[ "$CMD" =~ (\<\<|\|[[:space:]]*(psql|trino|clickhouse|snowsql|mysql|duckdb)|\<[[:space:]]*[^[:space:]]+\.sql) ]]; then |
There was a problem hiding this comment.
WARNING: \< is a GNU/glibc word-start assertion, so this branch is always true on Linux
[[ ... =~ ]] compiles with glibc regcomp, where \< matches the start of a word. \<\< therefore matches the empty string at any word start, so the second condition fires for every command that reached line 43. On glibc systems every command-position psql, trino, mysql, duckdb, snowsql, sqlcmd or clickhouse-client is denied - including the read-only invocations this file promises to leave alone (psql --version, psql -h db). The allow 'psql --version' case in tests/warehouse-guard.sh cannot pass there. BSD regex (macOS) treats \< as a literal <, so behaviour also differs by platform. Match the heredoc/redirect forms with plain << / < instead of backslash-escaped ones.
| # A client is only matched in command position (start, or after ; & | ( or sudo / docker exec), | ||
| # so file operations that merely mention a client name pass. | ||
| START='(^|[;&|(]|&&|\|\|)[[:space:]]*(sudo[[:space:]]+)?' | ||
| DOCKER='(docker|podman|kubectl)[[:space:]]+(exec|compose[[:space:]]+exec)[^;&|]*[[:space:]]' |
There was a problem hiding this comment.
SUGGESTION: the docker branch is not anchored to command position
$START carries the (^|[;&|(]) anchor, but the $DOCKER alternative does not, so docker exec ... <client> matches anywhere in the string. A command that merely mentions one, e.g. git commit -m 'docker exec -i pg psql -c select 1', is denied, contradicting the comment above about matching only in command position. Wrap the docker branch in the same anchor.
| find "$LOCK" -maxdepth 0 -mmin +10 -exec rmdir {} \; 2>/dev/null | ||
| if [ "$MODE" = "--wait" ]; then | ||
| # A refresh already in flight finishes first; this one then picks up anything newer. | ||
| for _ in $(seq 1 120); do mkdir "$LOCK" 2>/dev/null && break; sleep 1; done |
There was a problem hiding this comment.
WARNING: this wait loop does not record whether the lock was actually acquired
If an in-flight refresh outlasts the 120 attempts, the loop falls through, the command runs workspace refresh concurrently with it, and line 45 then rmdirs a lock owned by the other process, letting a third refresh start. The stale-lock cleanup on line 38 only reclaims locks older than ten minutes, so after a crashed refresh the two-minute wait always ends unlocked and two refreshes write the same snapshot. Separately, seq is not installed on stock macOS, where the loop body never runs at all. Set a LOCKED=1 flag when mkdir succeeds and only rmdir in that case, and use a portable counter.
| ids = list(manifest.get("skills", {})) | ||
| owner = (manifest.get("tenant"), str(manifest.get("apiUrl", "")).rstrip("/")) | ||
| except (OSError, ValueError, AttributeError): | ||
| return [] |
There was a problem hiding this comment.
WARNING: an unreadable snapshot is treated as another tenant and deletes every copied skill
return [] here - and signed_in_as() returning None when ~/.altimate/altimate.json cannot be read or parsed - makes sync() remove all marked .claude/skills/<name> copies. A manifest caught mid-write by a concurrent workspace refresh, or temporarily unreadable credentials, therefore wipes the team skills from the project. Because refresh-skills.sh --if-stale skips while .synced-at is under an hour old, they are not restored until the next stale refresh. This also contradicts the module docstring's 'a failure leaves existing copies as they were'. Return a sentinel (e.g. None) on read/parse failure and skip the removal pass instead of deleting.
| MANIFEST = ".manifest.json" | ||
| SKILLS_DIR = os.path.join(".claude", "skills") | ||
| MARKER = ".altimate-workspace-skill" | ||
| SNAPSHOT_META = {MANIFEST, ".gitignore", ".synced-at"} |
There was a problem hiding this comment.
SUGGESTION: SNAPSHOT_META is never used
Hidden files inside the bundle are already skipped by filename.startswith('.') in build_copy, so this constant is dead. Remove it or use it here so the snapshot-metadata invariant lives in one place.
| printf ' "hookEventName": "SessionStart",\n' | ||
| printf ' "additionalContext": "The altimate-code plugin ships two skills in this session. (1) The `altimate-code` skill routes data-engineering work to the altimate-code CLI — column-level lineage, downstream-impact, cross-database validation, FinOps, PII from sampled rows, query cost attribution, schema diffs, dev-vs-prod diffing. In a project linked to an Altimate workspace it also answers from the shared skills, knowledge and memory of that workspace, so questions about team conventions, standards or best practices fit it too. It is not well-suited for pure file/code edits to dbt models and SQL files that do not touch warehouse state — native Bash/Edit/Write handles those faster. (2) The `review` skill runs the altimate-code dbt PR reviewer on dbt project changes (models/**.sql, schema.yml, dbt_project.yml, sources.yml, macros, snapshots, seeds). It emits a signed verdict envelope with column lineage, query equivalence, dbt anti-pattern detection, and PII screening — none of which native tools or a generic code-review skill can compute. When the user asks to review dbt / SQL / warehouse changes, this is the specialized reviewer available. For non-dbt code review (TypeScript, Python, Go), the `review` skill declines."\n' | ||
| if [ "$MODE" = "cli" ]; then | ||
| printf ' "additionalContext": "The altimate-code plugin ships two skills in this session. (1) The `altimate-code` skill describes the model-free data tools of altimate-code: column-level lineage, downstream-impact analysis, PII detection, data-diff between tables or databases, FinOps and query cost, query equivalence, dialect translation, dbt manifest and lineage, and read-only queries and schema inspection on connected warehouses. They reach Claude in two ways. When the session has the plugin MCP tools (named `mcp__plugin_altimate-code_altimate-code__*`), those are the tools. When it does not, because the organisation disallows MCP servers or the server did not start, the same tools run from the shell through the altimate CLI `tool run` subcommand, which also returns structured output without a second agent. Neither mode runs a second LLM, and calls are read-only unless the user approves writes. In a project linked to an Altimate workspace, the `workspace_context`, `workspace_skill` and `workspace_execute` tools (MCP, or `tool run` with `--workspace-tools`) give the team conventions, knowledge, shared skills, project memory and read-only queries on the workspace warehouse integrations. Long multi-step jobs such as dbt builds and migrations can be delegated to the altimate-code agent. Plain file edits to dbt models and SQL files need none of this. (2) The `review` skill runs the altimate-code dbt PR reviewer on dbt project changes (models/**.sql, schema.yml, dbt_project.yml, sources.yml, macros, snapshots, seeds). It emits a signed verdict envelope with column lineage, query equivalence, dbt anti-pattern detection, and PII screening, which native tools and a generic code-review skill do not compute. For non-dbt code review (TypeScript, Python, Go), the `review` skill declines. In this session the altimate-code MCP server is not available, so the following project guidance applies. Data work in this project: the warehouses and dbt project of this repository are connected to Altimate Code through its CLI. When a request needs warehouse data or schema, column lineage, downstream impact, PII checks, data diffs or warehouse cost, use the Altimate Code CLI instead of ad-hoc database clients: run `altimate-code tool context` once, then `altimate-code tool run <tool> --workspace-tools --args <json>`; find a tool with `altimate-code tool list --workspace-tools` and its arguments with `altimate-code tool schema <tool> --workspace-tools`. `tool context` also names the linked workspace and lists its team skills. Questions about the team conventions, definitions or workspace data start with `tool run workspace_context --workspace-tools`; warehouse reads through the workspace engine use `tool run workspace_execute --workspace-tools`. It uses the configured connections and is read-only by default. Long multi-step jobs (dbt builds, migrations, backfills) use `altimate-code run`. Code edits, docs, tests and git work that need no warehouse data or lineage use the normal tools.%s"%s\n' "$SKILLS_NOTE" "$RELOAD" |
There was a problem hiding this comment.
SUGGESTION: the ~2.4 KB additionalContext text is duplicated
Line 57 repeats this entire string for MCP mode; the branches differ only in the trailing CLI-only guidance sentence. Two copies of a 2.4 KB prompt that already differ in intent will drift. Build the shared prefix once in a variable and append the mode-specific sentence plus $SKILLS_NOTE/$RELOAD.
| 5. For long multi-step jobs, or when you invoke **`/altimate-code:altimate <task>`** for work that needs the agent, Claude Code runs `altimate run "<your task>"` locally with the `builder` agent (or `analyst` for a data question when MCP is unavailable), which uses your configured LLM provider. | ||
| 6. Warehouse queries and dbt commands run **locally under your credentials**. If the installed CLI predates the `tool` subcommand, the skill falls back to delegation. | ||
|
|
||
| Connections come from project and global `.altimate-code/connections.json` files and `ALTIMATE_CODE_CONN_*` environment variables. To pin a file for the MCP server, change the `args` in `.mcp.json` to `["mcp", "serve", "--connections", "/absolute/path/to/connections.json"]`. |
There was a problem hiding this comment.
SUGGESTION: this example drops --workspace-tools
Following it disables the linked-workspace MCP tools this PR adds: .mcp.json and tests/workspace-tools.sh both require --workspace-tools, and CHANGELOG 1.4.0 states the server starts with it. Show ["mcp", "serve", "--workspace-tools", "--connections", "/absolute/path/to/connections.json"].
| grep -q "$tool" "$PLUGIN/skills/altimate-code/SKILL.md" || fail "SKILL.md does not describe $tool" | ||
| done | ||
|
|
||
| out="$(env -i HOME="$(mktemp -d)" PATH="/usr/bin:/bin" ALTIMATE_DATA_MODE=cli bash "$PLUGIN/hooks-handlers/session-start.sh")" |
There was a problem hiding this comment.
SUGGESTION: this runs the real SessionStart hook against the caller's working directory
With env -i but no CLAUDE_PROJECT_DIR and no cd, the hook falls back to $PWD, so it syncs .claude/skills in the checkout the suite was started from. If that checkout has a linked .altimate-code directory it also starts a real background workspace refresh and can remove marked skill copies outside the temp dir. The other hook tests pin CLAUDE_PROJECT_DIR to a mktemp project; do the same here (and clean up the mktemp -d HOME).
Code Review SummaryStatus: 9 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (18 files)
Fix these issues in Kilo Cloud Previous Review Summary (commit d6d440c)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit d6d440c)Status: 8 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (28 files)
Reviewed by deepseek-v4.1-flash-1017 · Input: 86.7K · Output: 52.1K · Cached: 1.6M |
…uring swaps, and judge warehouse clients by their own flags Workspace skill copies are served only for the signed-in account (tenant, URL and the CLI's credential digest), survive the CLI's snapshot swap, are never written through a symlinked .claude, are removed when copying is turned off, and carry a named marker. Refresh freshness is per account. The MCP mode check follows Claude Code's policy rules (managed-mcp.json, managed-settings.d, scoped plugin server name, serverCommand, merged allowlists, /mcp toggles by git root), and CLI-mode guidance names the executable on PATH. The opt-in warehouse guard checks each client call against its own query flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…recognise its staging names Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and partial reads Skills are served only to a signed-in account for a manifest the CLI would attribute; credentials or a snapshot that cannot be read change nothing, and a manifest replaced mid-sync stops the removal pass. The shared mkdir lock is renewed, taken over by rename and released only by its holder. BOM, symlink, Windows and special-file handling now give the same copies as the CLI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…; junctions on every Python version Each copy is swapped in only while the lock is held and the snapshot is the one read and still the signed-in account's. Swap detection uses the staging directory's mtime (Windows ctime is creation time). Junctions are refused at every path via st_reparse_tag, a SKILL.md that is not a regular file skips that skill, and marker lines split like the CLI's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| # A refresh already in flight finishes first; this one then picks up anything newer. | ||
| LOCKED= | ||
| for _ in $(seq 1 120); do mkdir "$LOCK" 2>/dev/null && LOCKED=1 && break; sleep 1; done | ||
| [ -n "$LOCKED" ] || { say "Another workspace skill refresh is still running; try again when it finishes."; exit 1; } |
There was a problem hiding this comment.
WARNING: seq is not installed on macOS, so the new LOCKED guard makes --wait fail there
seq ships with GNU coreutils, not macOS, so $(seq 1 120) expands to nothing and the loop body never runs. With the new [ -n "$LOCKED" ] check, /altimate-code:refresh-skills now prints "Another workspace skill refresh is still running" and exits 1 on every macOS run; before this change it fell through and refreshed. Use a portable counter (i=0; while [ "$i" -lt 120 ]; do ...; i=$((i + 1)); done) or jot.
| - **Command** — `plugins/altimate-code/commands/altimate.md` — `/altimate-code:altimate` slash command for explicit delegation. | ||
| - **Routing skill** — `plugins/altimate-code/skills/altimate-code/SKILL.md` — use the MCP tools when available, otherwise `tool run` (after `tool context`); delegate long jobs with `run`. | ||
| - **Analyst agent** — `plugins/altimate-code/agents/analyst.md` — read-only data analyst that works in both modes. | ||
| - **MCP registration** — `plugins/altimate-code/.mcp.json` — launches the local stdio server; builds without `mcp serve` fail to start it and the CLI mode applies instead. |
There was a problem hiding this comment.
SUGGESTION: CLI mode does not apply when the build lacks mcp serve
data-mode.sh chooses the mode from command -v altimate-code and the MCP policy settings only; it never runs the CLI, so it cannot detect a build without mcp serve. Such a build stays in MCP mode, the server fails to start, and the CLI routing guidance is not added (the PR's "Known limitations" says the same). plugins/altimate-code/README.md:14 repeats the claim. Reword both to say the skill's tool run fallback applies instead, or probe altimate-code mcp serve --help at session start.
| return False | ||
| if not os.path.isdir(target_root): | ||
| # Nothing to remove; create the directory only when there is something to copy. | ||
| snapshot = None if withdraw else read_snapshot(project) |
There was a problem hiding this comment.
SUGGESTION: read_snapshot parses the whole snapshot twice on the first sync
On a project without .claude/skills this reads the credentials and every SKILL.md, discards the result, then repeats the same read after the lock at line 428. The pre-lock call only decides whether the directory should be created, so a cheap read_manifest(project) is not None check would avoid the duplicate work.
| self.assertEqual(self.run_sync()[0], "changed") | ||
| self.assertFalse(os.path.exists(self.skill_path("staging-conventions"))) | ||
|
|
||
| def test_snapshot_without_a_tenant_is_not_served(self): |
There was a problem hiding this comment.
SUGGESTION: this test cannot detect a regression in the manifest check it names
The credentials written here (only altimateApiKey) make signed_in_as() raise Unreadable, so read_snapshot returns None and nothing is copied regardless of what owner_of does: deleting the tenant/apiUrl/account checks entirely would still pass. Keep the valid credentials from setUp, sync first, then strip tenant/apiUrl from the manifest and assert the existing copy is removed.
Summary
Gives Claude Code direct access to altimate-code's data tools instead of delegating each task to a second altimate-code agent with its own model. The plugin uses the altimate-code MCP server when it is allowed, falls back to the model-free
altimate-code tool runCLI when an organisation blocks MCP servers, and brings the linked Altimate workspace (knowledge, conventions, memory and team skills) into the session in both modes. Delegation to the altimate-code agent remains for long multi-step dbt jobs.Requires an altimate-code build with
mcp serveand thetoolCLI: AltimateAI/altimate-code#1609. Builds on #6 (MCP registration, analyst agent).Changes
Data tools: MCP first, CLI fallback
.mcp.jsonregistersaltimate-code mcp serve --workspace-tools(read-only by default).hooks-handlers/data-mode.sh) picks the mode without starting the CLI: CLI mode whenaltimate-codeis not on PATH, when managed, user or project settings deny the server (deniedMcpServers,allowedMcpServerswithout it,disabledMcpjsonServers), or withALTIMATE_DATA_MODE=cli. Only CLI mode adds routing guidance (tool context, thentool run <tool> --args <json>).altimate-codeskill, theanalystagent and/altimate-code:altimateuse the MCP tools when present andtool runotherwise; long jobs still use thebuilderagent.ALTIMATE_ROUTE_GUARD=1) points direct warehouse client commands attool run.Linked Altimate workspace in both modes
workspace_context,workspace_skillandworkspace_executeover MCP ortool run --workspace-tools, with handling for each setup status (needs_auth,needs_workspace,disabled,unavailable,needs_engine). Memory is saved only on an explicit request..claude/skills/, sanitised (frontmatter reduced to name and description,!command blocks disarmed, no hook, agent, command or settings files), only for the signed-in tenant, never replacing a user-written skill, and kept out of git. altimate-code #1609 also writes these copies when a project is linked or refreshed, so they exist before the first session./altimate-code:refresh-skillson demand.ALTIMATE_CLAUDE_SKILL_SYNC=0turns copying and refresh off.Also included
plugins/altimate-code/CHANGELOG.md.The automatic pilot listing (
altimate-code-experimental) from #9 is not part of this PR; it stays in #9.Validation
tests/data-mode.sh,tests/session-start.sh,tests/refresh-skills.sh,tests/warehouse-guard.sh,tests/resolve-cli.sh,tests/workspace-tools.shpass;python3 -m pytest tests/test_sync_workspace_skills.py17/17;claude plugin validatepasses for the marketplace and the plugin./altimate-code:refresh-skillsruns and reports the copies; MCP mode and CLI mode each answered workspace questions correctly against a #1609 build.Known limitations
altimate-codewithoutmcp serveis on PATH, the hook still picks MCP mode; the server fails to start and Claude Code caches that failure until restarted.altimate; MCP mode needs thealtimate-codename on PATH (CLI mode works with either).🤖 Generated with Claude Code