From b0d0a8dd4a05f14f5f8de588d263df8711c949d6 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Fri, 5 Jun 2026 18:33:31 -0700 Subject: [PATCH 1/7] fix: prepare dbt review action for v0.8.4 --- CHANGELOG.md | 11 +++++ README.md | 8 +++ docs/docs/usage/dbt-pr-review.md | 13 ++++- github/README.md | 2 +- github/review/examples/altimate-ingestion.yml | 2 +- packages/identity/mark-192x192.png | Bin 0 -> 144 bytes packages/identity/mark-512x512-light.png | Bin 0 -> 330 bytes packages/identity/mark-512x512.png | Bin 0 -> 330 bytes packages/identity/mark-96x96.png | Bin 0 -> 122 bytes packages/identity/mark-light.svg | 5 ++ packages/identity/mark.svg | 7 +++ .../src/altimate/review/orchestrate.ts | 4 +- .../opencode/src/altimate/review/runner.ts | 10 +++- packages/opencode/src/cli/cmd/github.ts | 3 +- .../test/altimate/review-runner.test.ts | 46 ++++++++++++++++++ .../opencode/test/altimate/review.test.ts | 24 ++++++++- .../opencode/test/cli/github-action.test.ts | 6 ++- .../test/install/repository-symlinks.test.ts | 16 ++++++ 18 files changed, 148 insertions(+), 9 deletions(-) create mode 100644 packages/identity/mark-192x192.png create mode 100644 packages/identity/mark-512x512-light.png create mode 100644 packages/identity/mark-512x512.png create mode 100644 packages/identity/mark-96x96.png create mode 100644 packages/identity/mark-light.svg create mode 100644 packages/identity/mark.svg create mode 100644 packages/opencode/test/altimate/review-runner.test.ts create mode 100644 packages/opencode/test/install/repository-symlinks.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fc78638b22..d5ef288484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.4] - Unreleased + +### Fixed + +- **The `github/review` composite action can be downloaded by GitHub Actions again.** The release tree contained three VS Code image symlinks whose `packages/identity` targets had been removed, causing GitHub's action downloader to reject the entire archive before the review step started. The identity assets are restored and a release-critical test now verifies every linked image resolves. +- **A valid dbt manifest is no longer mislabeled as a lint-only run.** Manifest availability is now checked independently from changed-model lookup, so new models and other valid manifests receive the correct full-run status. + +### Added + +- **Direct GitHub onboarding and a live dbt review demo.** The GitHub App installer now opens GitHub's repository-selection screen directly, and the README/docs link to the public `dbt-pr-review-demo` pull requests. + ## [0.8.3] - 2026-06-04 A plan-mode reliability patch for the hosted gateway and other non-Anthropic models. Ask the `plan` agent to plan something benign — *"plan a feature to add a verify-output button"* — on `altimate-backend/altimate-default` (GPT-5.x) and it could refuse outright with *"I'm sorry, but I cannot assist with that request."* This release fixes the refusal at its source, hardens the fix against prompt-injection, and rewrites a warning that was misdiagnosing the symptom. diff --git a/README.md b/README.md index 28754bd76d..9a4db181b7 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ into CI pipelines and orchestration DAGs. Precision data tooling for any LLM. [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) [![Slack](https://img.shields.io/badge/Slack-Join%20Community-4A154B?logo=slack)](https://altimate.studio/join-agentic-data-engineering-slack) [![Docs](https://img.shields.io/badge/docs-docs.altimate.sh-blue)](https://docs.altimate.sh) +[![Install GitHub App](https://img.shields.io/badge/GitHub-Install%20App-24292f?logo=github)](https://github.com/apps/altimate-code-agent/installations/new) +[![Live dbt review](https://img.shields.io/badge/dbt-Live%20PR%20Demo-ff694b?logo=dbt)](https://github.com/AltimateAI/dbt-pr-review-demo/pulls) @@ -36,6 +38,12 @@ curl -fsSL https://www.altimate.sh/install | bash The curl install drops a single self-contained binary named `altimate`. The npm install exposes both `altimate` and `altimate-code` on PATH; the curl install only exposes `altimate`. Alpine Linux (musl) and Windows on ARM64 are not currently supported by the standalone binary — use `apk add gcompat` on Alpine, or use WSL on Windows-on-ARM. +For GitHub, [install the Altimate Code App](https://github.com/apps/altimate-code-agent/installations/new) +to select repositories for interactive agent tasks. Automatic dbt pull-request +reviews use the deterministic GitHub Action documented below; see the +[public demo PRs](https://github.com/AltimateAI/dbt-pr-review-demo/pulls) before +installing it in your own repository. + Then — in order: **Step 1: Configure your LLM provider** (required before anything works): diff --git a/docs/docs/usage/dbt-pr-review.md b/docs/docs/usage/dbt-pr-review.md index 5373d98931..10f712c437 100644 --- a/docs/docs/usage/dbt-pr-review.md +++ b/docs/docs/usage/dbt-pr-review.md @@ -1,5 +1,14 @@ # dbt PR Review +[**See live review PRs**](https://github.com/AltimateAI/dbt-pr-review-demo/pulls) +· +[**Install the GitHub App**](https://github.com/apps/altimate-code-agent/installations/new) + +The public demo is a zero-secret DuckDB project with open PRs for broken joins, +removed tests, PII exposure, `SELECT *`, unsafe incremental models, and a safe +refactor. The GitHub App handles interactive repository tasks; the automatic +review on every pull request is installed with the Action below. + AI code review specialized for dbt/SQL. `dbt-pr-review` produces a single, **signed** verdict on a pull request — `APPROVE`, `COMMENT`, or `REQUEST_CHANGES` — where every **blocking** finding is backed by a deterministic engine call, not @@ -172,7 +181,7 @@ jobs: with: { fetch-depth: 0 } # Produce target/manifest.json for the full verdict (adapter-specific). - run: pip install dbt-core dbt-bigquery && dbt deps && dbt compile - - uses: AltimateAI/altimate-code/github/review@v1 + - uses: AltimateAI/altimate-code/github/review@v0.8.4 with: mode: comment # `gate` to block merges manifest_path: target/manifest.json @@ -270,7 +279,7 @@ In GitHub Actions, supply the connection from a secret — both sides of the dif run against the **same** warehouse (base-compiled vs head-compiled SQL): ```yaml - - uses: AltimateAI/altimate-code/github/review@v1 + - uses: AltimateAI/altimate-code/github/review@v0.8.4 with: mode: comment manifest_path: target/manifest.json diff --git a/github/README.md b/github/README.md index 3feb5a01c3..dcd2244db4 100644 --- a/github/README.md +++ b/github/README.md @@ -60,7 +60,7 @@ This will walk you through installing the GitHub app, creating the workflow, and ### Manual Setup -1. Install the GitHub app https://github.com/apps/altimate-code-agent. Make sure it is installed on the target repository. +1. [Install the GitHub app](https://github.com/apps/altimate-code-agent/installations/new) and select the target repository. 2. Add the following workflow file to `.github/workflows/altimate-code.yml` in your repo. Set the appropriate `model` and required API keys in `env`. ```yml diff --git a/github/review/examples/altimate-ingestion.yml b/github/review/examples/altimate-ingestion.yml index 2527451b3f..3e8ea12e0f 100644 --- a/github/review/examples/altimate-ingestion.yml +++ b/github/review/examples/altimate-ingestion.yml @@ -64,7 +64,7 @@ jobs: - name: altimate dbt PR review # Pin to the altimate-code release that ships `altimate review` # (the first release cut after the dbt-pr-review feature merges). - uses: AltimateAI/altimate-code/github/review@v0.8.0 + uses: AltimateAI/altimate-code/github/review@v0.8.4 with: mode: comment # start non-blocking; switch to `gate` once trusted manifest_path: target/manifest.json diff --git a/packages/identity/mark-192x192.png b/packages/identity/mark-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..071d18fe0c26fc8e8649c5b820fb28288767d548 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvK8A$4HYMBD0I0Jk_T!jS${{R0U6%o-b{`C$} z(8<%qF(l*O+v_`d859JV9cL}^T&3dbbmWfl?5N;|b`?+AS^Yp|b&b_*3=AC%*93ta lAeitdiizQZn8&1$6}&85GP1nyw=MvQd%F6$taD0e0suNRDX;(l literal 0 HcmV?d00001 diff --git a/packages/identity/mark-512x512-light.png b/packages/identity/mark-512x512-light.png new file mode 100644 index 0000000000000000000000000000000000000000..9f602d5ec3251a8b5e4fee9728b90cb0def4f217 GIT binary patch literal 330 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7?_xWRHv9tCXnI`@CkAK`{$3igv_(2&uW~@ zM1X>SJY5_^DsH{GYskx>AmZ%klO#0NBZK3x|E%ib_Tmewo|9Bi(Ue>B8G-unfG>=f z4;e^zyId+`NT&q*q%Xv(emj6i*Oz!%0# za(JYHQVZk@oban4DEqhJBQq<0RSoG42)Do7#?B535{9NfYuzIjfSGRhc3N(Ekga|L OB<|_z=d#Wzp$P!xU^p}Y literal 0 HcmV?d00001 diff --git a/packages/identity/mark-96x96.png b/packages/identity/mark-96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..b635c0759cc1f3ec9f6bd94e0442725b8c0b246b GIT binary patch literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeG3?%1&o4*=JaR&H=xC#ph{Qv(yDk7p={OcW{ zppK`DV~EG`WC_;8%q>cdft-JY3jVENF%(*TAR#0vq=~8XU>F0BYG + + + + diff --git a/packages/identity/mark.svg b/packages/identity/mark.svg new file mode 100644 index 0000000000..157edc4d75 --- /dev/null +++ b/packages/identity/mark.svg @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/packages/opencode/src/altimate/review/orchestrate.ts b/packages/opencode/src/altimate/review/orchestrate.ts index 7d20e41381..7f20911337 100644 --- a/packages/opencode/src/altimate/review/orchestrate.ts +++ b/packages/opencode/src/altimate/review/orchestrate.ts @@ -112,6 +112,8 @@ export interface CheckResult { /** High-level engine surface the orchestrator depends on. */ export interface ReviewRunner { + /** True when the configured dbt manifest loaded, independent of model lookup. */ + manifestAvailable?(): Promise impact(model: string): Promise grade(sql: string, dialect: string): Promise check(sql: string, dialect: string, baseSql?: string): Promise @@ -1001,7 +1003,7 @@ export async function runReview(input: OrchestrateInput): Promise f.kind === "model_sql" || f.kind === "python_model") const ctxByPath = new Map() - let anyManifest = false + let anyManifest = (await input.runner.manifestAvailable?.()) ?? false await Promise.all( modelFiles.map(async (file) => { const model = modelNameFromPath(file.path) diff --git a/packages/opencode/src/altimate/review/runner.ts b/packages/opencode/src/altimate/review/runner.ts index adb61b962c..1ffda22e6b 100644 --- a/packages/opencode/src/altimate/review/runner.ts +++ b/packages/opencode/src/altimate/review/runner.ts @@ -1,4 +1,5 @@ import { Dispatcher } from "../native" +import { parseManifest } from "../native/dbt/manifest" import type { CheckResult, EquivalenceResult, GradeResult, ImpactResult, ReviewRunner } from "./orchestrate" import { buildReviewSchemaContext, type SchemaContext } from "./schema-context" @@ -116,7 +117,10 @@ export function createDispatcherRunner(opts: DispatcherRunnerOptions): ReviewRun if (!manifestPromise) { manifestPromise = (async () => { try { - const res = await Dispatcher.call("dbt.manifest", { path: opts.manifestPath }) + // Manifest parsing is pure TypeScript. Keep it independent from the + // native dispatcher registration path so a core-loading failure + // cannot incorrectly downgrade a valid dbt run to lint-only. + const res = await parseManifest({ path: opts.manifestPath }) const models = new Map() const byName = new Map() const children = new Map() @@ -239,6 +243,10 @@ export function createDispatcherRunner(opts: DispatcherRunnerOptions): ReviewRun } return { + async manifestAvailable(): Promise { + return (await loadManifest()).ok + }, + async impact(model: string): Promise { const mf = await loadManifest() if (!mf.ok) { diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index 10914b22a2..8230ab7916 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -141,6 +141,7 @@ type IssueQueryResponse = { const AGENT_USERNAME = "altimate-code-agent[bot]" const AGENT_REACTION = "eyes" const WORKFLOW_FILE = ".github/workflows/altimate-code.yml" +export const GITHUB_APP_INSTALL_URL = "https://github.com/apps/altimate-code-agent/installations/new" // altimate_change end // Event categories for routing @@ -333,7 +334,7 @@ export const GithubInstallCommand = cmd({ // Open browser // altimate_change start — upstream_fix: GitHub App slug is altimate-code-agent - const url = "https://github.com/apps/altimate-code-agent" + const url = GITHUB_APP_INSTALL_URL // altimate_change end const command = process.platform === "darwin" diff --git a/packages/opencode/test/altimate/review-runner.test.ts b/packages/opencode/test/altimate/review-runner.test.ts new file mode 100644 index 0000000000..d4d6b4a72b --- /dev/null +++ b/packages/opencode/test/altimate/review-runner.test.ts @@ -0,0 +1,46 @@ +import { afterEach, describe, expect, test } from "bun:test" +import { mkdtempSync, rmSync, writeFileSync } from "node:fs" +import os from "node:os" +import path from "node:path" +import { createDispatcherRunner } from "../../src/altimate/review/runner" + +const tempDirs: string[] = [] + +afterEach(() => { + for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true }) +}) + +describe("review manifest loading", () => { + test("loads a valid manifest without initializing the native dispatcher", async () => { + const dir = mkdtempSync(path.join(os.tmpdir(), "altimate-review-manifest-")) + tempDirs.push(dir) + const manifestPath = path.join(dir, "manifest.json") + writeFileSync( + manifestPath, + JSON.stringify({ + metadata: { adapter_type: "duckdb" }, + nodes: { + "model.demo.orders": { + resource_type: "model", + name: "orders", + original_file_path: "models/orders.sql", + config: { materialized: "table" }, + depends_on: { nodes: [] }, + columns: {}, + }, + }, + sources: {}, + }), + ) + + const runner = createDispatcherRunner({ manifestPath }) + expect(await runner.manifestAvailable?.()).toBe(true) + expect(await runner.impact("orders")).toEqual({ + hasManifest: true, + severity: "SAFE", + directCount: 0, + transitiveCount: 0, + testCount: 0, + }) + }) +}) diff --git a/packages/opencode/test/altimate/review.test.ts b/packages/opencode/test/altimate/review.test.ts index 5a7eb099c2..62c3b30754 100644 --- a/packages/opencode/test/altimate/review.test.ts +++ b/packages/opencode/test/altimate/review.test.ts @@ -1079,7 +1079,7 @@ describe("orchestrate", () => { const runner: ReviewRunner = { ...fakeRunner({}), async impact() { - return { hasManifest: true, severity: "SAFE", directCount: 0, transitiveCount: 0, testCount: 0 } + return { hasManifest: false, severity: "UNKNOWN", directCount: 0, transitiveCount: 0, testCount: 0 } }, async equivalence() { return { decided: true, equivalent: false, differences: ["filter changed"], confidence: "high" } @@ -1229,6 +1229,28 @@ describe("orchestrate", () => { expect(["APPROVE", "COMMENT"]).toContain(env.verdict) }) + test("loaded manifest is not marked lint-only when a changed model is absent from it", async () => { + const files: ChangedFile[] = [{ path: "models/staging/new_model.sql", status: "added", diff: "+select 1\n" }] + const runner: ReviewRunner = { + ...fakeRunner({}), + async manifestAvailable() { + return true + }, + async impact() { + return { hasManifest: true, severity: "SAFE", directCount: 0, transitiveCount: 0, testCount: 0 } + }, + } + const env = await runReview({ + changedFiles: files, + config: { ...DEFAULT_REVIEW_CONFIG, reviewers: ["sql_quality"] }, + rubric: DEFAULT_RUBRIC, + mode: "comment", + runner, + getContent: content("select 1 as value"), + }) + expect(env.summary.degraded).toBe(false) + }) + test("renderSummary + inlineComments produce marker + structured output", async () => { const env = buildEnvelope({ findings: [ diff --git a/packages/opencode/test/cli/github-action.test.ts b/packages/opencode/test/cli/github-action.test.ts index 279ed27d08..a9e8ed97e5 100644 --- a/packages/opencode/test/cli/github-action.test.ts +++ b/packages/opencode/test/cli/github-action.test.ts @@ -1,8 +1,12 @@ import { test, expect, describe } from "bun:test" -import { extractResponseText, formatPromptTooLargeError } from "../../src/cli/cmd/github" +import { extractResponseText, formatPromptTooLargeError, GITHUB_APP_INSTALL_URL } from "../../src/cli/cmd/github" import type { MessageV2 } from "../../src/session/message-v2" import { SessionID, MessageID, PartID } from "../../src/session/schema" +test("GitHub App install URL opens the repository-selection flow", () => { + expect(GITHUB_APP_INSTALL_URL).toBe("https://github.com/apps/altimate-code-agent/installations/new") +}) + // Helper to create minimal valid parts function createTextPart(text: string): MessageV2.Part { return { diff --git a/packages/opencode/test/install/repository-symlinks.test.ts b/packages/opencode/test/install/repository-symlinks.test.ts new file mode 100644 index 0000000000..f9fc6a652a --- /dev/null +++ b/packages/opencode/test/install/repository-symlinks.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, test } from "bun:test" +import { existsSync, lstatSync, readlinkSync } from "node:fs" +import path from "node:path" + +const root = path.resolve(import.meta.dir, "../../../..") +const vscodeImages = ["button-dark.svg", "button-light.svg", "icon.png"] + +describe("release archive symlinks", () => { + test("VS Code image links resolve inside the repository", () => { + for (const name of vscodeImages) { + const link = path.join(root, "sdks/vscode/images", name) + expect(lstatSync(link).isSymbolicLink()).toBe(true) + expect(existsSync(path.resolve(path.dirname(link), readlinkSync(link)))).toBe(true) + } + }) +}) From 0503e7445225120ed7d683e603d1d84171429656 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Fri, 5 Jun 2026 18:48:41 -0700 Subject: [PATCH 2/7] fix: keep review action assets self-contained --- CHANGELOG.md | 2 +- packages/identity/mark-192x192.png | Bin 144 -> 0 bytes packages/identity/mark-512x512-light.png | Bin 330 -> 0 bytes packages/identity/mark-512x512.png | Bin 330 -> 0 bytes packages/identity/mark-96x96.png | Bin 122 -> 0 bytes packages/identity/mark-light.svg | 5 ----- packages/identity/mark.svg | 7 ------- .../test/install/repository-symlinks.test.ts | 13 +++++++------ sdks/vscode/images/button-dark.svg | 8 +++++++- sdks/vscode/images/button-light.svg | 6 +++++- sdks/vscode/images/icon.png | Bin 43 -> 330 bytes 11 files changed, 20 insertions(+), 21 deletions(-) delete mode 100644 packages/identity/mark-192x192.png delete mode 100644 packages/identity/mark-512x512-light.png delete mode 100644 packages/identity/mark-512x512.png delete mode 100644 packages/identity/mark-96x96.png delete mode 100644 packages/identity/mark-light.svg delete mode 100644 packages/identity/mark.svg mode change 120000 => 100644 sdks/vscode/images/button-dark.svg mode change 120000 => 100644 sdks/vscode/images/button-light.svg mode change 120000 => 100644 sdks/vscode/images/icon.png diff --git a/CHANGELOG.md b/CHANGELOG.md index d5ef288484..286591ead2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- **The `github/review` composite action can be downloaded by GitHub Actions again.** The release tree contained three VS Code image symlinks whose `packages/identity` targets had been removed, causing GitHub's action downloader to reject the entire archive before the review step started. The identity assets are restored and a release-critical test now verifies every linked image resolves. +- **The `github/review` composite action can be downloaded by GitHub Actions again.** The release tree contained three VS Code image symlinks whose removed targets caused GitHub's action downloader to reject the entire archive before the review step started. The images are now self-contained files and a release-critical test prevents dangling links from returning. - **A valid dbt manifest is no longer mislabeled as a lint-only run.** Manifest availability is now checked independently from changed-model lookup, so new models and other valid manifests receive the correct full-run status. ### Added diff --git a/packages/identity/mark-192x192.png b/packages/identity/mark-192x192.png deleted file mode 100644 index 071d18fe0c26fc8e8649c5b820fb28288767d548..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvK8A$4HYMBD0I0Jk_T!jS${{R0U6%o-b{`C$} z(8<%qF(l*O+v_`d859JV9cL}^T&3dbbmWfl?5N;|b`?+AS^Yp|b&b_*3=AC%*93ta lAeitdiizQZn8&1$6}&85GP1nyw=MvQd%F6$taD0e0suNRDX;(l diff --git a/packages/identity/mark-512x512-light.png b/packages/identity/mark-512x512-light.png deleted file mode 100644 index 9f602d5ec3251a8b5e4fee9728b90cb0def4f217..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 330 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7?_xWRHv9tCXnI`@CkAK`{$3igv_(2&uW~@ zM1X>SJY5_^DsH{GYskx>AmZ%klO#0NBZK3x|E%ib_Tmewo|9Bi(Ue>B8G-unfG>=f z4;e^zyId+`NT&q*q%Xv(emj6i*Oz!%0# za(JYHQVZk@oban4DEqhJBQq<0RSoG42)Do7#?B535{9NfYuzIjfSGRhc3N(Ekga|L OB<|_z=d#Wzp$P!xU^p}Y diff --git a/packages/identity/mark-96x96.png b/packages/identity/mark-96x96.png deleted file mode 100644 index b635c0759cc1f3ec9f6bd94e0442725b8c0b246b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeG3?%1&o4*=JaR&H=xC#ph{Qv(yDk7p={OcW{ zppK`DV~EG`WC_;8%q>cdft-JY3jVENF%(*TAR#0vq=~8XU>F0BYG - - - - diff --git a/packages/identity/mark.svg b/packages/identity/mark.svg deleted file mode 100644 index 157edc4d75..0000000000 --- a/packages/identity/mark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/packages/opencode/test/install/repository-symlinks.test.ts b/packages/opencode/test/install/repository-symlinks.test.ts index f9fc6a652a..465efb708f 100644 --- a/packages/opencode/test/install/repository-symlinks.test.ts +++ b/packages/opencode/test/install/repository-symlinks.test.ts @@ -1,16 +1,17 @@ import { describe, expect, test } from "bun:test" -import { existsSync, lstatSync, readlinkSync } from "node:fs" +import { existsSync, lstatSync, statSync } from "node:fs" import path from "node:path" const root = path.resolve(import.meta.dir, "../../../..") const vscodeImages = ["button-dark.svg", "button-light.svg", "icon.png"] -describe("release archive symlinks", () => { - test("VS Code image links resolve inside the repository", () => { +describe("release archive assets", () => { + test("VS Code images are self-contained regular files", () => { for (const name of vscodeImages) { - const link = path.join(root, "sdks/vscode/images", name) - expect(lstatSync(link).isSymbolicLink()).toBe(true) - expect(existsSync(path.resolve(path.dirname(link), readlinkSync(link)))).toBe(true) + const asset = path.join(root, "sdks/vscode/images", name) + expect(existsSync(asset)).toBe(true) + expect(lstatSync(asset).isSymbolicLink()).toBe(false) + expect(statSync(asset).size).toBeGreaterThan(0) } }) }) diff --git a/sdks/vscode/images/button-dark.svg b/sdks/vscode/images/button-dark.svg deleted file mode 120000 index c0e444a520..0000000000 --- a/sdks/vscode/images/button-dark.svg +++ /dev/null @@ -1 +0,0 @@ -../../../packages/identity/mark.svg \ No newline at end of file diff --git a/sdks/vscode/images/button-dark.svg b/sdks/vscode/images/button-dark.svg new file mode 100644 index 0000000000..157edc4d75 --- /dev/null +++ b/sdks/vscode/images/button-dark.svg @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/sdks/vscode/images/button-light.svg b/sdks/vscode/images/button-light.svg deleted file mode 120000 index 4120d51f62..0000000000 --- a/sdks/vscode/images/button-light.svg +++ /dev/null @@ -1 +0,0 @@ -../../../packages/identity/mark-light.svg \ No newline at end of file diff --git a/sdks/vscode/images/button-light.svg b/sdks/vscode/images/button-light.svg new file mode 100644 index 0000000000..ac619f1b2f --- /dev/null +++ b/sdks/vscode/images/button-light.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/sdks/vscode/images/icon.png b/sdks/vscode/images/icon.png deleted file mode 120000 index d6bfa6e7ca..0000000000 --- a/sdks/vscode/images/icon.png +++ /dev/null @@ -1 +0,0 @@ -../../../packages/identity/mark-512x512.png \ No newline at end of file diff --git a/sdks/vscode/images/icon.png b/sdks/vscode/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..48f38fc8c2be73e63990e01503af30b070c5be31 GIT binary patch literal 330 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7?_xWRHv9tCXnI`@Ck7h77+OV|9@0OM7Q|Y zJ3zrdo-U3d6}R5pHRNSb5OH?&NfMgsk->4;e^zyId+`NT&q*q%Xv(emj6i*Oz!%0# za(JYHQVZk@oban4DEqhJBQq<0RSoG42)Do7#?B535{9NfYuzIjfSGRhc3N(Ekga|L OB<|_z=d#Wzp$P!xU^p}Y literal 0 HcmV?d00001 From 780d5ac02411519ced9e7ef81207052a35168654 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Fri, 5 Jun 2026 19:14:24 -0700 Subject: [PATCH 3/7] fix: degrade safely on manifest probe errors --- .../src/altimate/review/orchestrate.ts | 3 ++- .../test/altimate/review-runner.test.ts | 17 ++++--------- .../opencode/test/altimate/review.test.ts | 24 ++++++++++++++++++- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/packages/opencode/src/altimate/review/orchestrate.ts b/packages/opencode/src/altimate/review/orchestrate.ts index 7f20911337..a025e67c80 100644 --- a/packages/opencode/src/altimate/review/orchestrate.ts +++ b/packages/opencode/src/altimate/review/orchestrate.ts @@ -1003,7 +1003,8 @@ export async function runReview(input: OrchestrateInput): Promise f.kind === "model_sql" || f.kind === "python_model") const ctxByPath = new Map() - let anyManifest = (await input.runner.manifestAvailable?.()) ?? false + const anyManifestFromRunner = await input.runner.manifestAvailable?.().catch(() => false) + let anyManifest = anyManifestFromRunner ?? false await Promise.all( modelFiles.map(async (file) => { const model = modelNameFromPath(file.path) diff --git a/packages/opencode/test/altimate/review-runner.test.ts b/packages/opencode/test/altimate/review-runner.test.ts index d4d6b4a72b..f055149fb8 100644 --- a/packages/opencode/test/altimate/review-runner.test.ts +++ b/packages/opencode/test/altimate/review-runner.test.ts @@ -1,20 +1,13 @@ -import { afterEach, describe, expect, test } from "bun:test" -import { mkdtempSync, rmSync, writeFileSync } from "node:fs" -import os from "node:os" +import { describe, expect, test } from "bun:test" +import { writeFileSync } from "node:fs" import path from "node:path" import { createDispatcherRunner } from "../../src/altimate/review/runner" - -const tempDirs: string[] = [] - -afterEach(() => { - for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true }) -}) +import { tmpdir } from "../fixture/fixture" describe("review manifest loading", () => { test("loads a valid manifest without initializing the native dispatcher", async () => { - const dir = mkdtempSync(path.join(os.tmpdir(), "altimate-review-manifest-")) - tempDirs.push(dir) - const manifestPath = path.join(dir, "manifest.json") + await using tmp = await tmpdir() + const manifestPath = path.join(tmp.path, "manifest.json") writeFileSync( manifestPath, JSON.stringify({ diff --git a/packages/opencode/test/altimate/review.test.ts b/packages/opencode/test/altimate/review.test.ts index 62c3b30754..0faf6a191e 100644 --- a/packages/opencode/test/altimate/review.test.ts +++ b/packages/opencode/test/altimate/review.test.ts @@ -1237,7 +1237,7 @@ describe("orchestrate", () => { return true }, async impact() { - return { hasManifest: true, severity: "SAFE", directCount: 0, transitiveCount: 0, testCount: 0 } + return { hasManifest: false, severity: "UNKNOWN", directCount: 0, transitiveCount: 0, testCount: 0 } }, } const env = await runReview({ @@ -1251,6 +1251,28 @@ describe("orchestrate", () => { expect(env.summary.degraded).toBe(false) }) + test("manifest availability errors degrade safely instead of aborting the review", async () => { + const files: ChangedFile[] = [{ path: "models/staging/stg_x.sql", status: "modified", diff: "+select 1\n" }] + const runner: ReviewRunner = { + ...fakeRunner({}), + async manifestAvailable() { + throw new Error("manifest unreadable") + }, + async impact() { + return { hasManifest: false, severity: "UNKNOWN", directCount: 0, transitiveCount: 0, testCount: 0 } + }, + } + const env = await runReview({ + changedFiles: files, + config: { ...DEFAULT_REVIEW_CONFIG, reviewers: ["sql_quality"] }, + rubric: DEFAULT_RUBRIC, + mode: "comment", + runner, + getContent: content("select 1 as value"), + }) + expect(env.summary.degraded).toBe(true) + }) + test("renderSummary + inlineComments produce marker + structured output", async () => { const env = buildEnvelope({ findings: [ From 5803b2f04f30b046549b0c1fda75c4e072c2b6d5 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Fri, 5 Jun 2026 19:24:08 -0700 Subject: [PATCH 4/7] fix: guard optional manifest availability hook --- packages/opencode/src/altimate/review/orchestrate.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/altimate/review/orchestrate.ts b/packages/opencode/src/altimate/review/orchestrate.ts index a025e67c80..a72778a642 100644 --- a/packages/opencode/src/altimate/review/orchestrate.ts +++ b/packages/opencode/src/altimate/review/orchestrate.ts @@ -1003,8 +1003,10 @@ export async function runReview(input: OrchestrateInput): Promise f.kind === "model_sql" || f.kind === "python_model") const ctxByPath = new Map() - const anyManifestFromRunner = await input.runner.manifestAvailable?.().catch(() => false) - let anyManifest = anyManifestFromRunner ?? false + let anyManifest = false + if (input.runner.manifestAvailable) { + anyManifest = await input.runner.manifestAvailable().catch(() => false) + } await Promise.all( modelFiles.map(async (file) => { const model = modelNameFromPath(file.path) From 26206e0a67494ce968cd8101876d85f90a0a7297 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Fri, 5 Jun 2026 22:06:51 -0700 Subject: [PATCH 5/7] test: make review e2e assertion platform-stable --- .../opencode/test/skill/release-v0.8.5-adversarial.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/opencode/test/skill/release-v0.8.5-adversarial.test.ts b/packages/opencode/test/skill/release-v0.8.5-adversarial.test.ts index 03a91d2c7a..4d3e1deb85 100644 --- a/packages/opencode/test/skill/release-v0.8.5-adversarial.test.ts +++ b/packages/opencode/test/skill/release-v0.8.5-adversarial.test.ts @@ -7,6 +7,7 @@ import { describe, expect, test } from "bun:test" import fs from "node:fs/promises" import path from "node:path" import YAML from "yaml" +import { collectChangedFiles } from "../../src/altimate/review/git" import { reviewPullRequest } from "../../src/altimate/review/run" import { tmpdir } from "../fixture/fixture" @@ -239,6 +240,9 @@ describe("v0.8.5 end-to-end - real review pipeline", () => { await $`git commit -m base`.cwd(tmp.path).quiet() await Bun.write(modelPath, "select 1 as order_id, 'paid' as status\n") + const changed = await collectChangedFiles({ cwd: tmp.path, base: "HEAD" }) + expect(changed.map((file) => file.path)).toEqual(["models/orders.sql"]) + const result = await reviewPullRequest({ cwd: tmp.path, base: "HEAD", @@ -249,7 +253,6 @@ describe("v0.8.5 end-to-end - real review pipeline", () => { expect(result.summary.degraded).toBe(false) expect(result.manifestHash).toMatch(/^[a-f0-9]{16}$/) - expect(["lite", "full"]).toContain(result.tier) expect(["APPROVE", "COMMENT"]).toContain(result.verdict) }) }) From 10214aa3b94eb90353face09dc9e3209cf74e989 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Fri, 5 Jun 2026 22:21:04 -0700 Subject: [PATCH 6/7] test: isolate action review e2e process --- .../skill/release-v0.8.5-adversarial.test.ts | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/packages/opencode/test/skill/release-v0.8.5-adversarial.test.ts b/packages/opencode/test/skill/release-v0.8.5-adversarial.test.ts index 4d3e1deb85..81e66f41e1 100644 --- a/packages/opencode/test/skill/release-v0.8.5-adversarial.test.ts +++ b/packages/opencode/test/skill/release-v0.8.5-adversarial.test.ts @@ -6,9 +6,8 @@ import { $ } from "bun" import { describe, expect, test } from "bun:test" import fs from "node:fs/promises" import path from "node:path" +import { pathToFileURL } from "node:url" import YAML from "yaml" -import { collectChangedFiles } from "../../src/altimate/review/git" -import { reviewPullRequest } from "../../src/altimate/review/run" import { tmpdir } from "../fixture/fixture" const repoRoot = path.resolve(import.meta.dir, "../../../..") @@ -240,18 +239,47 @@ describe("v0.8.5 end-to-end - real review pipeline", () => { await $`git commit -m base`.cwd(tmp.path).quiet() await Bun.write(modelPath, "select 1 as order_id, 'paid' as status\n") - const changed = await collectChangedFiles({ cwd: tmp.path, base: "HEAD" }) - expect(changed.map((file) => file.path)).toEqual(["models/orders.sql"]) + const runnerPath = path.join(tmp.path, "run-review-e2e.ts") + await Bun.write( + runnerPath, + ` + import { collectChangedFiles } from ${JSON.stringify(pathToFileURL(path.join(repoRoot, "packages/opencode/src/altimate/review/git.ts")).href)} + import { reviewPullRequest } from ${JSON.stringify(pathToFileURL(path.join(repoRoot, "packages/opencode/src/altimate/review/run.ts")).href)} - const result = await reviewPullRequest({ - cwd: tmp.path, - base: "HEAD", - manifestPath: "target/manifest.json", - mode: "comment", - noAi: true, + const cwd = process.argv[2] + const changed = await collectChangedFiles({ cwd, base: "HEAD" }) + if (JSON.stringify(changed.map((file) => file.path)) !== JSON.stringify(["models/orders.sql"])) { + throw new Error("changed files mismatch: " + JSON.stringify(changed)) + } + const result = await reviewPullRequest({ + cwd, + base: "HEAD", + manifestPath: "target/manifest.json", + mode: "comment", + noAi: true, + }) + console.log(JSON.stringify({ + degraded: result.summary.degraded, + manifestHash: result.manifestHash, + verdict: result.verdict, + })) + `, + ) + const proc = Bun.spawn(["bun", runnerPath, tmp.path], { + cwd: path.join(repoRoot, "packages/opencode"), + stdout: "pipe", + stderr: "pipe", + env: { ...process.env }, }) + const [stdout, stderr, exitCode] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + proc.exited, + ]) - expect(result.summary.degraded).toBe(false) + expect(exitCode, stderr).toBe(0) + const result = JSON.parse(stdout) + expect(result.degraded).toBe(false) expect(result.manifestHash).toMatch(/^[a-f0-9]{16}$/) expect(["APPROVE", "COMMENT"]).toContain(result.verdict) }) From 9bca0c5aeb2ab290a64e94c77cdc157bffe645e4 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Fri, 5 Jun 2026 22:50:21 -0700 Subject: [PATCH 7/7] fix: bound action release lookup --- github/review/action.yml | 6 +++- .../skill/release-v0.8.5-adversarial.test.ts | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/github/review/action.yml b/github/review/action.yml index 0f01ccc7b2..bb0b9e4db6 100644 --- a/github/review/action.yml +++ b/github/review/action.yml @@ -64,7 +64,11 @@ runs: if [[ "${ACTION_REF:-}" =~ ^v?([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then VERSION="${BASH_REMATCH[1]}" else - VERSION=$(curl -sf https://api.github.com/repos/AltimateAI/altimate-code/releases/latest | grep -o '"tag_name": *"[^"]*"' | cut -d'"' -f4 || true) + VERSION=$( + curl -sf --connect-timeout 5 --max-time 15 \ + https://api.github.com/repos/AltimateAI/altimate-code/releases/latest \ + | grep -o '"tag_name": *"[^"]*"' | cut -d'"' -f4 || true + ) VERSION="${VERSION#v}" # the installer's --version expects no leading 'v' fi echo "version=${VERSION:-latest}" >> $GITHUB_OUTPUT diff --git a/packages/opencode/test/skill/release-v0.8.5-adversarial.test.ts b/packages/opencode/test/skill/release-v0.8.5-adversarial.test.ts index 81e66f41e1..b4b72eddce 100644 --- a/packages/opencode/test/skill/release-v0.8.5-adversarial.test.ts +++ b/packages/opencode/test/skill/release-v0.8.5-adversarial.test.ts @@ -79,6 +79,34 @@ describe("v0.8.5 adversarial - composite action", () => { expect(await fs.stat(curlMarker).then(() => true).catch(() => false)).toBe(false) }) + test("the non-semver action ref fallback bounds the release lookup", async () => { + await using tmp = await tmpdir() + const bin = path.join(tmp.path, "bin") + const output = path.join(tmp.path, "github-output") + const argsPath = path.join(tmp.path, "curl-args") + await fs.mkdir(bin) + await Bun.write( + path.join(bin, "curl"), + `#!/usr/bin/env bash\nprintf '%s\\0' "$@" > "$CURL_ARGS"\nprintf '{"tag_name":"v0.8.5"}\\n'\n`, + ) + await fs.chmod(path.join(bin, "curl"), 0o755) + + const result = await runBash(await actionScript("Get altimate-code version"), { + ACTION_REF: "main", + CURL_ARGS: argsPath, + GITHUB_OUTPUT: output, + PATH: `${bin}:${process.env.PATH}`, + }) + + expect(result.exitCode, result.stderr).toBe(0) + expect(await fs.readFile(output, "utf8")).toBe("version=0.8.5\n") + const curlArgs = (await fs.readFile(argsPath, "utf8")).split("\0").filter(Boolean) + expect(curlArgs).toContain("--connect-timeout") + expect(curlArgs).toContain("5") + expect(curlArgs).toContain("--max-time") + expect(curlArgs).toContain("15") + }) + test("hostile refs and paths are forwarded as data, never evaluated by bash", async () => { await using tmp = await tmpdir() const bin = path.join(tmp.path, "bin")