@altimateai/altimate-code 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -0
- package/LICENSE +0 -1
- package/bin/altimate +38 -29
- package/dbt-tools/dist/altimate_python_packages/dbt_core_integration.py +17 -1
- package/dbt-tools/dist/altimate_python_packages/dbt_utils.py +29 -2
- package/dbt-tools/dist/index.js +532 -407
- package/package.json +9 -13
- package/postinstall.mjs +41 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,122 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.1] - 2026-05-20
|
|
9
|
+
|
|
10
|
+
A focused provider-error pass plus the standalone-binary fix: the curl-installed binary now starts (previously crashed with `Cannot find module '@altimateai/altimate-core'`), is renamed to match the npm primary `bin` (`altimate-code` → `altimate` for the curl path only), and Alpine + Windows-on-ARM hit a clear early-exit instead of a cryptic gzip failure. Two 5-persona pre-release reviews (provider-error pass, then binary-fix + rename pass) drove the surface — 86 adversarial tests total pin the regression classes.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Curl-installed standalone binary no longer crashes with `Cannot find module '@altimateai/altimate-core'` on first run.** The `script/build.ts` marked altimate-core as `external` (NAPI native modules can't live inside Bun's single-file bunfs), and the release archive shipped only the raw Bun binary — no companion `node_modules`, no NODE_PATH-aware wrapper. CI smoke tests hid the bug by pre-setting NODE_PATH against the developer checkout before invoking the binary. The fix stages a per-target copy of altimate-core whose loader is rewritten to a one-line shim `module.exports = require('./altimate-core.<platform>.node')`, drops the matching `.node` file next to it, and uses a `Bun.build` `onResolve` plugin to redirect every `@altimateai/altimate-core` import to that shim. Bun statically sees a single require and embeds that one `.node` into bunfs. Result: ~176 MB self-contained binary, no companion files, no NODE_PATH dance. CI smoke tests now run with `NODE_PATH` cleared from `$RUNNER_TEMP`, plus an independent `strings`-based content assertion that exactly one platform `.node` is embedded — the v0.5.10 / v0.7.0 class of regression is pinned by three independent guards. (#820)
|
|
15
|
+
- **Alpine Linux (musl) and Windows on ARM64 install paths now fail fast with actionable messages instead of silent 404 → tar/unzip errors.** Pre-fix, `curl … | bash` on an unsupported target would write GitHub's 404 HTML to disk and die "not in gzip format". The curl `install` script, the npm bin wrapper (`packages/opencode/bin/altimate`), the npm `postinstall.mjs`, and `script/build.ts` all detect these platforms early and point to `apk add gcompat` (Alpine) or x64 emulation / WSL (Windows ARM). The musl-detection logic is also `pipefail`-safe (`ldd --version` exits non-zero on musl by design; the previous pipeline-form inherited that failure and silently missed every non-Alpine musl distro). (#820)
|
|
16
|
+
- **Curl install `--fail` on both download paths.** A 404 / WAF block / TLS-rewriting corporate proxy no longer writes the error page to disk and gets unzipped as a binary; `curl --fail` exits non-zero and the install bails cleanly. (#820)
|
|
17
|
+
- **`script/build.ts --target-index=N` for an out-of-range index exits non-zero.** Pre-fix, after the musl/win32-arm64 cull, an invalid index silently produced zero artifacts and CI "succeeded" with no binary. (#820)
|
|
18
|
+
- **`script/build.ts --single` on a musl-linux host refuses to build the unrunnable glibc target.** Pre-fix the build would succeed but the resulting binary couldn't load on the host. (#820)
|
|
19
|
+
- **`Installation.method()` recognises `~/.altimate/bin` as a curl install.** The same release that renames the curl-install dir would have broken `altimate upgrade` for curl-installed users without this; the `.opencode/bin` and `.local/bin` branches stay for back-compat. (#820)
|
|
20
|
+
- **Provider 4xx errors now show the inner error message instead of a raw JSON dump.** When any provider returned the standard `{error: {message, type, code}}` shape (OpenAI, Azure OpenAI, OpenRouter, etc.), `parseAPICallError`'s extraction chain short-circuited on the truthy parent `error` object, the `typeof errMsg === "string"` guard rejected it, and the parser fell through to dumping the raw response body — which appeared as `APIError: Bad Request: {?:?}` after telemetry redaction collapsed string values to `?`. Telemetry caught users retrying broken model selections 3+ times in the same session because the surfaced error gave no clue about the cause. Users now see actionable text such as `APIError: Bad Request: The model 'gpt-5-codex' does not exist or you do not have access to it.` The OR-chain is replaced with explicit-typeof ternaries that mirror `parseStreamError`'s pattern, so a truthy non-string at any tier cannot block a valid string further down the chain. (#789, closes #788)
|
|
21
|
+
- **Bedrock / AWS Lambda `errorMessage` shape is now extracted.** AWS APIs that return `{errorMessage: "...", errorType: "..."}` (Lambda style) previously fell through the OpenAI/Anthropic-shaped chain to a raw-body dump. Added `body.errorMessage` to the extraction ladder in both `parseAPICallError` and `parseStreamError`.
|
|
22
|
+
- **Streaming error path no longer dumps `Unknown: {"type":"error",...}` for non-OpenAI codes.** `parseStreamError` previously handled only 4 OpenAI error codes (`context_length_exceeded`, `insufficient_quota`, `usage_not_included`, `invalid_prompt`); everything else fell through to `JSON.stringify(e)`. Added a default fallback that runs the same string-typeof chain as `parseAPICallError`, so any extractable provider message becomes a clean api_error.
|
|
23
|
+
- **`model_not_found` no longer triggers a silent retry storm.** OpenAI 404s are forced retryable in general (some legitimate models 404 transiently), but `error.code === "model_not_found"` now short-circuits to `isRetryable: false` — the user sees the actionable error on attempt 1 instead of after 5 silent retries.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **`altimate models` discoverability hint on model-not-found errors.** When `error.code === "model_not_found"`, the surfaced message now ends with `Run \`altimate models\` to see available models.` so the next step is one command away.
|
|
28
|
+
- **Provider-API-Errors troubleshooting reference** at `docs/docs/reference/troubleshooting.md` covering model-not-found, unauthorized, rate-limited, context-overflow, and HTML-page error classes.
|
|
29
|
+
- **Install-path troubleshooting section** at `docs/docs/reference/troubleshooting.md` covering "standalone binary not found after curl install" (the `altimate-code` → `altimate` rename), the legacy `Cannot find module '@altimateai/altimate-core'` crash with recovery instructions, Alpine/musl unsupported (with `apk add gcompat` workaround), and Windows-on-ARM unsupported (with WSL workaround). README also documents the curl-install option alongside the npm one.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **Curl-installed binary renamed `altimate-code` → `altimate`** to match the npm package's primary `bin` entry. The npm path continues to expose **both** `altimate` and `altimate-code`, so existing `npm install -g`/`pnpm i -g` users see no behavioural change. Homebrew installs are unchanged (the formula installs `altimate` and symlinks `altimate-code` for back-compat). Only the standalone (`curl … | bash`) channel is affected — it now ships a single self-contained `altimate` binary to `~/.altimate/bin/` (was `~/.altimate-code/bin/altimate-code`). CI users with scripts that called `altimate-code` after the curl install should switch to `altimate` or install via npm. The install script removes any stale `~/.altimate/bin/altimate-code` left over from a pre-v0.7.1 install. (#820)
|
|
34
|
+
- **`check_version` probes both `altimate` and `altimate-code` on PATH** so an upgrade from v0.7.0 doesn't always re-download even when the version already matches. (#820)
|
|
35
|
+
- **Curl install final banner mirrors the npm postinstall**: `altimate`, `altimate run "hello"`, `altimate --help`, and the same `https://altimate-code.dev` docs URL. Rosetta-detected x64 → arm64 swap now emits a one-line muted notice instead of being silent. (#820)
|
|
36
|
+
- **`Installation.method()` upgrade detection recognises `~/.altimate/bin`** as a curl install (in addition to the legacy `~/.opencode/bin` and `~/.local/bin` paths). (#820)
|
|
37
|
+
- **`_requiredExports` literal extracted from `@altimateai/altimate-core/index.js` is JSON.parsed and shape-checked** before being inlined into the per-target staged shim. Pre-fix, the regex match group was inlined verbatim — a malicious altimate-core that published an `index.js` whose `_requiredExports = ["x"]; <attacker JS>; const _foo = [` form would have embedded attacker JavaScript into every shipped binary. The validator now requires a pure JSON array of non-empty short string literals; anything else aborts the build. (#820)
|
|
38
|
+
- **`build.ts` asserts the on-disk `@altimateai/altimate-core` version matches `package.json` declaration** after `bun install --os=* --cpu=*`. Catches the stale-hoist scenario where a previous version lingers in `node_modules/.bun/` and the new build silently embeds yesterday's `.node`. (#820)
|
|
39
|
+
- **Per-target staging dir is wiped before each build** (pre-loop cleanup), not just after, so a previous build that crashed between staging and post-build cleanup can never leak a stale `.altimate-core-staged/` into the next build's resolution. (#820)
|
|
40
|
+
- **Curl install extracts only the expected binary member** from tar/zip archives (`tar --no-same-owner -xzf … "$binary_name"` / `unzip … "$binary_name"`), so a future build mistake that tars a directory of attacker-controlled paths can't write them outside the explicit member. (#820)
|
|
41
|
+
- **`install_from_binary` guards against cp-on-self**: `--binary ~/.altimate/bin/altimate` no longer truncates the destination to empty via POSIX `cp` semantics. (#820)
|
|
42
|
+
- **Build matrix and standalone install matrix now align** — only platforms with a published `@altimateai/altimate-core` NAPI prebuild produce a release archive. (#820)
|
|
43
|
+
|
|
44
|
+
### Removed
|
|
45
|
+
|
|
46
|
+
- **`linux-arm64-musl`, `linux-x64-musl`, `linux-x64-baseline-musl`, and `win32-arm64` archives** from the release build matrix. `@altimateai/altimate-core` has no NAPI prebuild for these targets; archives for them were never going to work. The npm wrapper (`bin/altimate`) and npm `postinstall.mjs` hard-error on these platforms with the same `apk add gcompat` / WSL workarounds the curl-install script uses. Re-added when upstream prebuilds ship. (#820)
|
|
47
|
+
|
|
48
|
+
### Privacy
|
|
49
|
+
|
|
50
|
+
- **`Telemetry.maskString` now redacts email addresses and internal hostnames.** Pre-fix, the JSON-quote masking rule incidentally collapsed everything inside provider error JSON to `?`. The provider-error fix unwraps that JSON, which means provider-side identifiers (caller emails, internal `*.local` / `*.internal` / RFC1918 / IPv6 loopback / ULA / link-local / AWS IMDS endpoints) now flow as plain English. Added explicit redaction patterns so they're masked before reaching telemetry, the share backend, or local session storage. The masker is kept in sync with `parseAPICallError`'s `maskInternalHost` (same internal-endpoint coverage); query-string and fragment characters (`+`, `#`, `,`, `;`) are inside the trailing char class so secrets past the `<internal-host>` marker don't survive. `sk-…` and `Bearer …` token redaction is unchanged.
|
|
51
|
+
- **`metadata.url` on `MessageV2.APIError` masks internal hosts and strips basic-auth userinfo.** When `error.url` points at `localhost`, `*.local`, `*.internal`, an RFC1918 IPv4, IPv6 loopback / ULA / link-local, or the AWS IMDS address (`169.254.169.254`), the host is rewritten to `internal-host.redacted` before the URL lands on the parsed error. Basic-auth userinfo (`user:pass@…`) is stripped on **every** URL — internal or public — since a credential in a public-host URL is at least as risky as one in an internal proxy. Public-host URLs are otherwise preserved verbatim for debugging.
|
|
52
|
+
- **`responseBody` is capped at 4KB** at the `parseAPICallError` boundary. Without this, a hostile or verbose gateway could persist a 100KB+ body into local storage and (for shared sessions) the share backend.
|
|
53
|
+
|
|
54
|
+
### Testing
|
|
55
|
+
|
|
56
|
+
- 46 adversarial tests covering JSON-scalar bodies, prototype-pollution attempts, 100KB error messages, malformed JSON, every-tier null/numeric extraction, Bedrock `errorMessage` precedence, the `parseStreamError` fallback for unknown codes, the `model_not_found` retry-storm carve-out, the `altimate models` hint, the responseBody cap, the metadata.url internal-host masking (incl. IPv6 loopback/ULA/link-local, AWS IMDS, public-host basic-auth userinfo strip, RFC1918 boundary checks, lookalike-hostname guards), and the new email / internal-host `maskString` patterns (incl. IMDS, IPv6, and query-fragment leak guards).
|
|
57
|
+
- 48 adversarial tests in `release-v0.7.1-binary-adversarial.test.ts` pinning the binary-fix + rename surface — install-method upgrade-path detection (`.altimate`/`.opencode`/`.local` triple-cover), curl-install hardening (Rosetta notice, cp-on-self guard, stale `altimate-code` cleanup, explicit tar/zip member extraction, dual `check_version` probe, musl + npm gcompat messaging, `--fail` on both curl paths, `pipefail`-safe ldd capture, no musl target-suffix construction), `_requiredExports` JSON.parse + shape-check rejection, altimate-core version pinning + actionable rebuild hint, staging-dir pre-loop wipe + post-build cleanup, empty-targets and musl-host build guards, build matrix excluding linux-musl + win32-arm64, smoke-test hermeticity (host-platform `findLocalBinary` filter + tmpdir cwd + content-level `strings` assertion), npm-wrapper + postinstall fail-fast parity for musl + win32-arm64, troubleshooting and README doc surface, archive-name + bin-rename cross-file invariants, and `release.yml` hermetic CI smoke tests + narrowed `publish-npm` permissions. Tests run together with the provider-error suite as the release-critical gate (`test/branding/ test/install/ test/skill/release-v0.7.1*`).
|
|
58
|
+
- Smoke tests (`test/install/smoke-test-binary.test.ts`) gained: hermetic `NODE_PATH`-cleared invocation from a fresh tmpdir (so Bun's compiled binary cannot walk the worktree for `node_modules`), and a content-level `strings`-based assertion that exactly one platform `.node` is embedded in the compiled binary — independent of any runtime resolution path, so a silent `onResolve` regression that embeds 5 platforms' worth of `.node` files would fire here even if the runtime test passes by accident. (#820)
|
|
59
|
+
|
|
60
|
+
## [0.7.0] - 2026-05-03
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- **Bridged to OpenCode upstream v1.4.0 across a history rewrite.** Upstream rewrote git history between v1.3.17 and v1.4.0 (2026-04-04), leaving zero common ancestor with our fork. A new `script/upstream/bridge-merge.ts` tool overlays v1.4.0's tree file-by-file: take v1.4.0's content for unmarked upstream-shared files, keep main's version entirely for files carrying `altimate_change` markers (61 files), keep `keepOurs` files unchanged (65 files), and drop excluded `skipFiles` (3676 files). PR #18186 (Anthropic provider exclusion handling) is reapplied programmatically. Net diff vs v0.6.1: 361 files, +88,823 / -34,416 LoC. The merge passed nine adversarial audit cycles and ships a 234-test bridge-merge regression suite plus 76-test E2E suite that fails CI loudly on every regression class we hit. (#757)
|
|
65
|
+
- **Pinned the dependency floor for the bridge merge.** Added overrides for `effect@4.0.0-beta.43`, `@effect/platform-node@4.0.0-beta.43`, and `@effect/platform-node-shared@4.0.0-beta.43` (beta.58 removed `ServiceMap` in favor of `Context`). Held back `@ai-sdk/*`, `@openrouter/ai-sdk-provider`, and `@opentui/*` from upstream's bumps. Restored upstream's `solid-js@1.9.10` patchedDependencies declaration (Solid Transition correctness, issue #2046).
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- **`--dangerously-skip-permissions` flag on `altimate run`** (upstream PR #21266 backport). Aliased to our existing `--yolo` / `ALTIMATE_CLI_YOLO` branch. Strictly safer than upstream's version: `--yolo` honors explicit `deny` rules in session config, upstream's auto-approves anything not denied with no notion of deny rules. Documented in `docs/docs/usage/cli.md`.
|
|
70
|
+
- **`variant_list` TUI keybind** (upstream PR #21185 backport). Opens the "Switch model variant" dialog (`/variants`); hidden when no variants are configured. Schema entry in `config.ts`; documented in `docs/docs/configure/keybinds.md`.
|
|
71
|
+
- **Plain-text rate-limit retry detection** (upstream PR #21355 backport). `session/retry.ts` now retries Alibaba/DashScope and other providers that return non-JSON 429 responses.
|
|
72
|
+
- **Telemetry diagnostic fields on `agent_outcome`.** `final_tool`, `error_class`, and `reason` are now populated for non-completed outcomes (was empty in ~30% of `abandoned`/`aborted`/`error` runs). `reason` is PII-masked at extraction (capped 500 chars for `error`, 200 for `aborted`); `final_tool` includes MCP-namespaced names (e.g. `mcp__atlassian__getJiraIssue`); `error_class` derives from `classifyError` patterns. Documented in `docs/docs/reference/telemetry.md`.
|
|
73
|
+
- **`bridge-guard` skill** (`.claude/commands/bridge-guard.md`). Codifies the v1.4.0 audit playbook into a reusable runbook covering split-brain module pairs, brand-leak patterns, behavior-patch loss, logo/color regressions, internal-script binary names, and the three-layer safety net. Invoke as `/bridge-guard [PR#]`.
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- **Permission split-brain — infinite ask loop.** v1.4.0 introduced an Effect-TS `Permission` service in `src/permission/index.ts`, but every runtime ask site still called `PermissionNext.ask` from the older module in `src/permission/next.ts`. The two modules each owned their own pending map: asks landed in `PermissionNext`'s map, the HTTP reply route looked them up in the Effect service's empty map, returned 200 without resolving the deferred. User-visible: clicking "Allow once" / "Allow always" / "Reject" did nothing, the prompt kept re-rendering. Routed both reply routes plus `GET /permission` to `PermissionNext`. Pinned with 4 static-text invariants, 1 tree-wide import scan, and 3 runtime end-to-end deadlock tests.
|
|
78
|
+
- **`mcp add` lost its 7-flag non-interactive mode.** Bridge merge overwrote `McpAddCommand` with upstream's interactive-only version. Scripts/CI calling `mcp add --name foo --type remote --url …` either hung (no TTY) or silently dropped into prompts ignoring args. Restored full `--name --type --url --command --header --oauth --global` flag block with markers.
|
|
79
|
+
- **`mcp remove` command (and `rm` alias) restored** — entirely lost during v1.4.0 merge. `mcp remove <not-found>` now exits non-zero (was exiting 0 because `process.exit(1)` inside the async Effect chain was swallowed).
|
|
80
|
+
- **`SessionStatus.set` / `cancel` async drift.** v1.4.0 made these async but six callers in `prompt.ts` and `processor.ts` were unawaited, racing the idle-state flush on shutdown. All call sites now await; `defer(cancel)` switched to `await using`.
|
|
81
|
+
- **`tool/plan.ts` PlanExitTool reject semantics.** v1.4.0 changed reject from `answer !== "Yes"` to `answer === "No"`, so dialog cancel / dismiss / network drop silently confirmed the agent transition (unsafe). Reverted to "reject on anything but explicit Yes".
|
|
82
|
+
- **SQLite `IMMEDIATE` transaction was silently `DEFERRED`.** `Database.transaction()` wrapper didn't accept the `behavior` option, so `SyncEvent.run`'s read-then-write sequence fell back to deferred locking — concurrent runs could interleave and produce duplicate sequence numbers / corrupt event log. Pass-through added; runtime test exercises 10 parallel writers to prove serialization.
|
|
83
|
+
- **Plugin hook isolation.** `Plugin.trigger()` invoked each hook's callback with no try/catch — a single buggy plugin (third-party or local: codex/copilot/gitlab/etc.) would crash the entire LLM call with a confusing unrelated stack. Wrapped in try/catch with logged failure; iteration continues across remaining hooks.
|
|
84
|
+
- **HTML escaping in OAuth/codex callback templates.** `${error}` was interpolated raw in `mcp/oauth-callback.ts` (real XSS) and `plugin/codex.ts`. Added `escapeHtml()` helper, applied at every interpolation.
|
|
85
|
+
- **Symlink escape in `containsPath` boundary checks.** `project/instance.ts` and `plugin/shared.ts` reverted to `Filesystem.contains()` (lexical) during the bridge merge; restored to `Filesystem.containsReal()` (resolves symlinks before checking project boundary).
|
|
86
|
+
- **Effect Service identifier collision (`@opencode/Account`).** `account/index.ts` and `account/service.ts` both registered the same id with two parallel ManagedRuntimes (undefined merge behaviour). Deleted dead `effect/runtime.ts` and `account/service.ts`; renamed `auth/index.ts` to `@opencode/Auth.cli` to disambiguate from the live `auth/service.ts`.
|
|
87
|
+
- **`chat.params` `maxOutputTokens` hook silently ignored.** `session/llm.ts` computed `maxOutputTokens` *after* the hook fired and never read back from `params.maxOutputTokens` — codex/copilot's own `output.maxOutputTokens = undefined` was a no-op, and any third-party plugin altering this field did nothing. Now matches upstream PRs #21220 + #21225.
|
|
88
|
+
- **`File.search()` race with initial scan.** Cache was empty when tests called search before the initial scan completed. `files()` now tracks the in-flight scan with a `pending` promise and awaits it on call.
|
|
89
|
+
- **`MessageV2.fromError` regressions.** "OAuth token refresh failed" now returns `MessageV2.AuthError` instead of `UnknownError`. `errorMessage()` surfaces stack location for empty-message Error instances. Context-overflow detection added for OpenAI-style `context_length_exceeded` codes.
|
|
90
|
+
- **`workspace-router-middleware.ts` typeerror under `OPENCODE_EXPERIMENTAL_WORKSPACES=1`.** Adaptor API was renamed `fetch → target` in v1.4.0; rewrote to `adaptor.target()` + `ServerProxy.http()` and skip routing for local targets.
|
|
91
|
+
- **`Truncate.init()` lost from `project/bootstrap.ts`.** Hourly scheduler that prunes `Global.Path.data/tool-output/tool_*` never re-registered, so the directory grew unboundedly. Restored.
|
|
92
|
+
- **`provider/models.ts` `setTimeout(…, 0)` deferral on initial refresh.** Removed during merge, re-introducing the circular-dep risk fixed by altimate `980efaab64`. Restored.
|
|
93
|
+
- **TUI mid-render display corruption.** `clipboard.ts`, `dialog-workspace-list.tsx`, `dialog-mcp.tsx` had `console.log` writing directly to the terminal mid-render. Restored structured `Log.Default.debug` calls and removed a stray workspace-result `JSON.stringify` debug-print.
|
|
94
|
+
- **Workspace SSE reconnect loop killed by transient network blips.** `control-plane/workspace.ts` lost its defensive `.catch(() => undefined)` and a `return` from the local-workspace branch was permanent rather than per-iteration. Restored both.
|
|
95
|
+
- **Branding restoration after v1.4.0 leaks.** Fixed in three batches: cycle-7 audit (196 leaks across 66 files: `$schema` URLs, system prompts, repo references, generated SDK strings, route descriptions); ASCII logo (`ALTIMATE | CODE` block letters were swapped for `OPEN | CODE`); brand colors on the startup logo (`theme.primary` warm orange + `theme.accent` purple were replaced with monochrome grey/white); `attach.ts` Basic-auth username (was `opencode:`, broke authentication against altimate-code servers); `cli/cmd/github.ts` AGENT_USERNAME / WORKFLOW_FILE / GitHub App slug / OIDC audience / branch prefix; CLI describe text in 8+ subcommands; `mcp` resolveConfigPath order (`altimate-code.json` precedence with `opencode.json` fallback for existing installs); plugin install path (`.altimate-code/` for new installs, keep `.opencode/` if it exists).
|
|
96
|
+
- **API-key leakage in masked strings.** `maskString` only redacted quoted spans. Added unquoted-API-key (`sk-`, `sk-ant-`, `sk-proj-`) and `Bearer …` patterns with a 20-char length floor (avoids false positives on short identifiers like `sk-foo`). All 9 existing `maskString` consumers pick this up.
|
|
97
|
+
- **`ERROR_PATTERNS` coverage for HTTP 5xx + rate-limit prose.** Real provider errors like `APIError: Service unavailable (503)` and `Rate limit exceeded. Retry after 60s` were classifying as `unknown` because patterns only matched the prefixed forms. Added phrases: `service unavailable`, `rate limit`, `rate_limit`, `retry after`, `too many requests`, `503`, `502`, `504` to the `http_error` class.
|
|
98
|
+
- **`marker-guard` strict mode on bridge-merge pushes to main.** PR-side guard already runs non-strict for `upstream/merge-*` head_refs, but the squash-merge push event has no head_ref — was failing on the hundreds of upstream files the bridge brings in. Now detects bridge/upstream-merge commits in the pushed range by subject (`grep -qiE '(bridge|merge) upstream'`) and downgrades strict→non-strict. (#782, closes #781)
|
|
99
|
+
|
|
100
|
+
### Security
|
|
101
|
+
|
|
102
|
+
- **API-key redaction at the diagnostic surface.** `Telemetry.deriveAgentOutcomeReason()` applies `maskString` to both `error` and `aborted` reasons before they enter the `agent_outcome.reason` field (which surfaces in our backend telemetry). `prompt.ts` extracts only `err.data.message` instead of `JSON.stringify(error.data)` (which leaked `responseBody` / `responseHeaders` / metadata).
|
|
103
|
+
- **Plugin hook crash containment.** A single plugin throwing in `chat.params` / `chat.headers` / any registered hook no longer takes down the entire LLM call (see Fixed).
|
|
104
|
+
|
|
105
|
+
### Internal
|
|
106
|
+
|
|
107
|
+
- **Three-layer regression backstop for bridge merges.** (1) `script/upstream/analyze.ts --branding` now catches bare `opencode` strings in user-visible contexts (yargs `describe`, console output, MCP `clientInfo.name`, User-Agent, OIDC audiences, GitHub workflow YAML, `spawn` binary names); skips lines inside `altimate_change` blocks. (2) New `MergeConfig.requireMarkers` allowlist of 38 files known to hold altimate behavioral patches; `bridge-merge.ts` treats them as `keepOurs` and hard-aborts the merge if any is missing markers. (3) Retroactive marker sweep added markers to 14 previously-unmarked files. CI runs `--branding` and `--require-markers --strict` on every PR.
|
|
108
|
+
- **9 audit cycles documented and pinned.** 310 tests across 8 files in `test/upstream/` (`bridge-merge`, `bridge-merge-invariants`, `bridge-merge-runtime`, `bridge-merge-v3`, `bridge-merge-e2e`, `v140-merge-adversarial`, `v140-merge-fuzz`, `v140-merge-chaos`, `v140-permission-deadlock`).
|
|
109
|
+
- **`tracing.ts` `flushSync` vs async `snapshot` race.** Crashed-trace file could be clobbered by an in-flight async snapshot completing its `fs.rename` after `flushSync`'s sync write. Added a `crashed` flag with two checkpoint reads on the async path. Plus `Recap.flush()` helper to replace `setTimeout(50)` waits in tests on slow CI runners.
|
|
110
|
+
- **GitGuardian whitelisting.** `.gitguardian.yaml` paths-ignore for the `protected.ts` deny-list (filenames like `.pgpass` / `id_rsa` / `credentials.json` are filenames, not credentials); split filename literals via `DOT + "name"` concatenation so the GG dashboard doesn't keep flagging them; replaced JWT-shaped synthetic tokens in the v140 adversarial suite with generic 36-char alphanumeric tokens.
|
|
111
|
+
- **`.github/meta/{commit,diff,pr-body-*}` untracked + gitignored.** Per CLAUDE.md these are transient scratch files; tracked instances were leaking false-positive branding hits and stale commit messages. Pattern broadened to `**/.github/meta/...` so nested paths are covered.
|
|
112
|
+
- **`globalThis.fetch` leak in `session-proxy-middleware.test.ts`.** Test stubbed `fetch` but never restored it, cascading into ~15 unrelated test failures (OAuth, ECONNRESET, HttpExporter, Discovery, live-trace-viewer). Now snapshots and restores in `afterEach`.
|
|
113
|
+
- **Restored `.github/TEAM_MEMBERS`.** Bridge merge replaced our 30-name list with upstream's 15-name list, removing 28 altimate maintainers including `anandgupta42` — `pr-standards.yml` was flagging every team member's PR with `needs:title` / `needs:compliance` and auto-closing after 2 hours.
|
|
114
|
+
|
|
115
|
+
### Testing
|
|
116
|
+
|
|
117
|
+
- **200+ new tests pinning every audit-cycle fix:** static-text invariants for service identifiers, async signatures, marker integrity, branding; runtime tests for `Database.transaction` IMMEDIATE serialization, `BusEvent.define` idempotency, `SyncEvent` ⊆ `BusEvent` registry bridge, `PlanExitTool` reject semantics, plugin trigger isolation; property-based fuzz of `maskString` (5×500 random iterations on unicode/control-chars/emoji), `deriveAgentOutcomeReason` field bounds; chaos: 100 parallel `deriveAgentOutcomeReason` calls, regex DoS resistance with relative-budget thresholds; E2E: spawns the CLI as a real user (`bun run src/index.ts <args>` with isolated `XDG_*` env vars) and visually inspects `--version`, `--help`, `mcp --help`, theme schemas, system-prompt files, etc.
|
|
118
|
+
- **Hardened flaky tests against parallel-suite contention.** Extracted pure helpers from `tui/thread.ts` to remove `mock.module()` calls that leaked across files. Replaced FIFO request-matching queue in `session/llm.test.ts` with a path-keyed Map (out-of-order requests no longer resolve another test's deferred). Added explicit `30_000ms` timeouts to slow-SDK tests. Local full suite: 7965 pass / 0 fail.
|
|
119
|
+
|
|
120
|
+
### Documentation
|
|
121
|
+
|
|
122
|
+
- **README refreshed for v0.5.12 through v0.6.1** — adds three Key Features entries (cross-dialect data parity, automated dbt unit tests, GitLab MR review), four providers (Altimate LLM Gateway, Databricks AI Gateway, Snowflake Cortex, LM Studio), Microsoft Fabric warehouse footnote, `/data-parity` and `/dbt-unit-tests` quick-demo examples, inline list of the 19 built-in skills. Source of truth is CHANGELOG.md; bullets are condensed restatements. (#784)
|
|
123
|
+
|
|
8
124
|
## [0.6.1] - 2026-04-24
|
|
9
125
|
|
|
10
126
|
### Fixed
|
package/LICENSE
CHANGED
package/bin/altimate
CHANGED
|
@@ -153,42 +153,51 @@ function supportsAvx2() {
|
|
|
153
153
|
return false
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
function isMusl() {
|
|
157
|
+
if (platform !== "linux") return false
|
|
158
|
+
try {
|
|
159
|
+
if (fs.existsSync("/etc/alpine-release")) return true
|
|
160
|
+
} catch {
|
|
161
|
+
// ignore
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
165
|
+
const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase()
|
|
166
|
+
if (text.includes("musl")) return true
|
|
167
|
+
} catch {
|
|
168
|
+
// ignore
|
|
169
|
+
}
|
|
170
|
+
return false
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// @altimateai/altimate-core has no NAPI prebuild for musl or win32-arm64,
|
|
174
|
+
// and the altimate binary embeds altimate-core's .node file at build time.
|
|
175
|
+
// Hard-error early instead of letting findBinary() walk the whole tree and
|
|
176
|
+
// emit a misleading "package manager failed to install the right version"
|
|
177
|
+
// message — the right diagnosis is that these platforms aren't built.
|
|
178
|
+
if (isMusl()) {
|
|
179
|
+
console.error("altimate-code is not currently supported on Alpine Linux (musl).")
|
|
180
|
+
console.error("Workarounds:")
|
|
181
|
+
console.error(" • apk add gcompat # run glibc binaries on Alpine")
|
|
182
|
+
console.error(" • Use a glibc-based container (debian/ubuntu/alpine+gcompat)")
|
|
183
|
+
process.exit(1)
|
|
184
|
+
}
|
|
185
|
+
if (platform === "windows" && arch === "arm64") {
|
|
186
|
+
console.error("altimate-code is not currently built for Windows on ARM64.")
|
|
187
|
+
console.error("Run the x64 build under Windows ARM's x64 emulation, or use WSL.")
|
|
188
|
+
process.exit(1)
|
|
189
|
+
}
|
|
190
|
+
|
|
156
191
|
const names = (() => {
|
|
157
192
|
const avx2 = supportsAvx2()
|
|
158
193
|
const baseline = arch === "x64" && !avx2
|
|
159
194
|
|
|
160
195
|
if (platform === "linux") {
|
|
161
|
-
const musl = (() => {
|
|
162
|
-
try {
|
|
163
|
-
if (fs.existsSync("/etc/alpine-release")) return true
|
|
164
|
-
} catch {
|
|
165
|
-
// ignore
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
try {
|
|
169
|
-
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
170
|
-
const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase()
|
|
171
|
-
if (text.includes("musl")) return true
|
|
172
|
-
} catch {
|
|
173
|
-
// ignore
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return false
|
|
177
|
-
})()
|
|
178
|
-
|
|
179
|
-
if (musl) {
|
|
180
|
-
if (arch === "x64") {
|
|
181
|
-
if (baseline) return [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
|
|
182
|
-
return [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
|
|
183
|
-
}
|
|
184
|
-
return [`${base}-musl`, base]
|
|
185
|
-
}
|
|
186
|
-
|
|
187
196
|
if (arch === "x64") {
|
|
188
|
-
if (baseline) return [`${base}-baseline`, base
|
|
189
|
-
return [base, `${base}-baseline
|
|
197
|
+
if (baseline) return [`${base}-baseline`, base]
|
|
198
|
+
return [base, `${base}-baseline`]
|
|
190
199
|
}
|
|
191
|
-
return [base
|
|
200
|
+
return [base]
|
|
192
201
|
}
|
|
193
202
|
|
|
194
203
|
if (arch === "x64") {
|
|
@@ -24,7 +24,7 @@ from typing import (
|
|
|
24
24
|
|
|
25
25
|
import agate
|
|
26
26
|
import json
|
|
27
|
-
from dbt.adapters.factory import get_adapter, register_adapter
|
|
27
|
+
from dbt.adapters.factory import FACTORY, get_adapter, register_adapter
|
|
28
28
|
from dbt.config.runtime import RuntimeConfig
|
|
29
29
|
from dbt.flags import set_from_args
|
|
30
30
|
from dbt.parser.manifest import ManifestLoader, process_node
|
|
@@ -319,6 +319,22 @@ class DbtProject:
|
|
|
319
319
|
self.config = RuntimeConfig.from_args(self.args)
|
|
320
320
|
if hasattr(self.config, "source_paths"):
|
|
321
321
|
self.config.model_paths = self.config.source_paths
|
|
322
|
+
# dbt's adapter factory silently short-circuits register_adapter() if
|
|
323
|
+
# an adapter of the same type is already registered, so a target
|
|
324
|
+
# switch that keeps the adapter type (e.g. two Databricks targets or
|
|
325
|
+
# two DuckDB targets with different credentials) would otherwise
|
|
326
|
+
# leave us with a stale adapter still bound to the previous target's
|
|
327
|
+
# host / http_path / path. Evict it here before re-registering so
|
|
328
|
+
# the new config's credentials actually take effect. See
|
|
329
|
+
# AltimateAI/vscode-dbt-power-user#1676 and #1737.
|
|
330
|
+
adapter_name = self.config.credentials.type
|
|
331
|
+
if adapter_name in FACTORY.adapters:
|
|
332
|
+
try:
|
|
333
|
+
FACTORY.adapters[adapter_name].cleanup_connections()
|
|
334
|
+
except Exception:
|
|
335
|
+
pass
|
|
336
|
+
with FACTORY.lock:
|
|
337
|
+
FACTORY.adapters.pop(adapter_name, None)
|
|
322
338
|
if DBT_MAJOR_VER >= 1 and DBT_MINOR_VER >= 8:
|
|
323
339
|
from dbt.mp_context import get_mp_context
|
|
324
340
|
register_adapter(self.config, get_mp_context())
|
|
@@ -16,6 +16,9 @@ try:
|
|
|
16
16
|
except ImportError:
|
|
17
17
|
HAS_AGATE = False
|
|
18
18
|
|
|
19
|
+
# JavaScript Number.MAX_SAFE_INTEGER — beyond this, floats lose precision
|
|
20
|
+
MAX_SAFE_INTEGER = 9007199254740991
|
|
21
|
+
|
|
19
22
|
|
|
20
23
|
def to_dict(obj, visited=None):
|
|
21
24
|
if visited is None:
|
|
@@ -28,16 +31,40 @@ def to_dict(obj, visited=None):
|
|
|
28
31
|
|
|
29
32
|
if HAS_AGATE and isinstance(obj, agate.Table):
|
|
30
33
|
visited.add(obj_id)
|
|
34
|
+
rows = [to_dict(row, visited) for row in obj.rows]
|
|
35
|
+
# Detect columns with large integers that were converted to strings
|
|
36
|
+
column_types = [x.__class__.__name__ for x in obj.column_types]
|
|
37
|
+
for col_idx, col_type in enumerate(column_types):
|
|
38
|
+
if col_type in ("Number", "Integer") and any(
|
|
39
|
+
isinstance(row[col_idx], str) for row in rows if row[col_idx] is not None
|
|
40
|
+
):
|
|
41
|
+
column_types[col_idx] = "BigInteger"
|
|
31
42
|
result = {
|
|
32
|
-
"rows":
|
|
43
|
+
"rows": rows,
|
|
33
44
|
"column_names": obj.column_names,
|
|
34
|
-
"column_types":
|
|
45
|
+
"column_types": column_types,
|
|
35
46
|
}
|
|
36
47
|
visited.remove(obj_id)
|
|
37
48
|
return result
|
|
38
49
|
if isinstance(obj, str):
|
|
39
50
|
return obj
|
|
51
|
+
# Encode binary payloads as lowercase hex strings BEFORE falling into the
|
|
52
|
+
# Iterable branch below. Otherwise ``bytes`` gets expanded into a
|
|
53
|
+
# ``list[int]`` (one int per byte), which for real binary columns blows
|
|
54
|
+
# past python-bridge's IPC pipe buffer and surfaces as
|
|
55
|
+
# ``Error [ERR_IPC_DISCONNECTED]`` on the extension side. See
|
|
56
|
+
# AltimateAI/vscode-dbt-power-user#1747. ``memoryview`` is normalized
|
|
57
|
+
# through ``bytes()`` first because some adapters hand back views over
|
|
58
|
+
# a pyarrow buffer rather than a raw ``bytes`` object.
|
|
59
|
+
if isinstance(obj, (bytes, bytearray)):
|
|
60
|
+
return obj.hex()
|
|
61
|
+
if isinstance(obj, memoryview):
|
|
62
|
+
return bytes(obj).hex()
|
|
63
|
+
if isinstance(obj, int) and abs(obj) > MAX_SAFE_INTEGER:
|
|
64
|
+
return str(obj)
|
|
40
65
|
if isinstance(obj, Decimal):
|
|
66
|
+
if obj == obj.to_integral_value() and abs(obj) > MAX_SAFE_INTEGER:
|
|
67
|
+
return str(int(obj))
|
|
41
68
|
return float(obj)
|
|
42
69
|
if isinstance(obj, (datetime, date, time)):
|
|
43
70
|
return obj.isoformat()
|