altimate-code 0.8.7 → 0.8.9

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 CHANGED
@@ -5,6 +5,48 @@ 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.8.9] - 2026-06-19
9
+
10
+ ### Fixed
11
+
12
+ - **The non-interactive `run` guard no longer mis-handles a blank env var.** A blank/whitespace `ALTIMATE_NON_INTERACTIVE=` is now treated as unset, so it can't silently reintroduce the headless `run` hang that #937 fixed. (#937 follow-up)
13
+ - **Session transcript boolean query flags accept the common falsey strings.** `thinking`, `toolDetails`, and `assistantMetadata` now treat `false`/`0`/`no`/`off` (case-insensitive) as false, not just the literal `"false"`. (#941 follow-up)
14
+ - **Writing an MCP server entry refuses to clobber an unparseable config** (detected via `parse()` with an error sink, not the error-tolerant `parseTree()`), and `updatedAt` is preserved through config normalization so the datamate reconnect signal survives. (#893 follow-up)
15
+ - **dbt error bubbling strips the full ANSI escape family** (cursor/erase sequences from progress spinners), not just SGR color codes. (#933 follow-up)
16
+
17
+ ### Changed
18
+
19
+ - **The headless `serve` startup upgrade check is jittered** (base + random, ~1–6s) so a fleet restarting together doesn't stampede the unauthenticated GitHub releases API in the same instant. (#940 follow-up)
20
+
21
+ ### Internal
22
+
23
+ - **Diverged upstream constants are wrapped in `altimate_change` markers** (`DEFAULT_CHUNK_TIMEOUT`, the question-tool `output` field) so they survive upstream merges. (#844, #937)
24
+ - **Added a v0.8.9 adversarial test suite** covering the shipping code and the review fixes under hostile input (question-tool env matrix, MCP config parse-guard, startup-upgrade fail-safety + jitter).
25
+
26
+ ## [0.8.8] - 2026-06-19
27
+
28
+ ### Added
29
+
30
+ - **Windows now has a one-line PowerShell installer.** `irm https://www.altimate.sh/install.ps1 | iex` downloads the standalone binary from GitHub releases (AVX2-aware, with a baseline fallback), adds it to your user `PATH`, and supports `-Version`, `-NoPathUpdate`, and `-ForceBaseline`. `altimate upgrade` uses the same path on Windows. (#930)
31
+ - **IDE-aware datamate transport, MCP enabled-state persistence, and a `/mcps` command.** The active IDE's `mcp.json` datamate entry/URL is mirrored into `altimate-code.json` and reconnected when it changes; enabled/disabled state for MCP servers now persists to disk; `/mcps` lists and toggles servers; project `mcp.json` files are discovered recursively (project-scoped servers are discovered disabled, never auto-connected). (#893)
32
+ - **Session transcript REST endpoint for the datamates extension.** `GET /session/:id/transcript` returns the full conversation as formatted Markdown, with `thinking`, `toolDetails`, and `assistantMetadata` query flags. (#941)
33
+
34
+ ### Changed
35
+
36
+ - **Headless `serve` now checks for and applies updates on startup.** When `autoupdate` is unset (the default) and a supported install method is present, `serve` performs a one-shot, best-effort self-upgrade shortly after start. It never restarts the running process and can never crash `serve` (errors are swallowed). Opt out with `autoupdate: false` or `"notify"` in config, or `OPENCODE_DISABLE_AUTOUPDATE=1`. (#940)
37
+ - **The per-chunk streaming watchdog timeout was raised from 2 minutes to 5 minutes** so slow warehouse/LLM streams are not aborted prematurely. (#844)
38
+
39
+ ### Fixed
40
+
41
+ - **`dbt show`/`compile` failures now surface the real dbt error** instead of a generic "Could not parse", with inline SQL redacted from the message so secrets/PII don't leak into logs. (#933)
42
+ - **The question tool no longer hangs in non-interactive contexts.** `altimate run` is detected as headless and returns "Unanswered" (letting the agent decide) rather than blocking forever; `ALTIMATE_AUTO_ANSWER=first|last|<label>` can pre-answer. (#937)
43
+ - **Windows release archives are verified by checksum in both installers** before extraction, and latest-version resolution is more resilient to transient fetch failures. (#942, #946)
44
+
45
+ ### Internal
46
+
47
+ - **`serve` logs the trace directory on startup** so operators can find session traces. (#929)
48
+ - **Added a release-validation regression suite** covering the merged PRs. (#952)
49
+
8
50
  ## [0.8.7] - 2026-06-10
9
51
 
10
52
  ### Added
package/README.md CHANGED
@@ -30,13 +30,19 @@ into CI pipelines and orchestration DAGs. Precision data tooling for any LLM.
30
30
  npm install -g altimate-code
31
31
  ```
32
32
 
33
- Or via curl (installs the `altimate` binary to `~/.altimate/bin`):
33
+ Or via curl on macOS/Linux (installs the `altimate` binary to `~/.altimate/bin`):
34
34
 
35
35
  ```bash
36
36
  curl -fsSL https://www.altimate.sh/install | bash
37
37
  ```
38
38
 
39
- 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.
39
+ On Windows, install the same self-contained binary (to `%USERPROFILE%\.altimate\bin`) from PowerShellno Node required:
40
+
41
+ ```powershell
42
+ powershell -c "irm https://www.altimate.sh/install.ps1 | iex"
43
+ ```
44
+
45
+ The standalone install drops a single self-contained binary named `altimate`. The npm install exposes both `altimate` and `altimate-code` on PATH; the standalone 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.
40
46
 
41
47
  For GitHub, [install the Altimate Code App](https://github.com/apps/altimate-code-agent/installations/new)
42
48
  to select repositories for interactive agent tasks. Automatic dbt pull-request
@@ -321,16 +321,25 @@ function getDbt() {
321
321
  }
322
322
  return resolvedDbt;
323
323
  }
324
+ function toExecFileError(e) {
325
+ if (e instanceof Error)
326
+ return e;
327
+ return new Error(String(e));
328
+ }
324
329
  function run2(args) {
325
330
  const dbt2 = getDbt();
326
331
  const env = buildDbtEnv(dbt2);
327
332
  const cwd = globalOptions.projectRoot ?? process.cwd();
328
333
  return new Promise((resolve4, reject2) => {
329
334
  execFile(dbt2.path, args, { timeout: 120000, maxBuffer: 10 * 1024 * 1024, env, cwd }, (err, stdout, stderr) => {
330
- if (err)
331
- reject2(err);
332
- else
335
+ if (err) {
336
+ const execErr = err;
337
+ execErr.stdout = stdout;
338
+ execErr.stderr = stderr;
339
+ reject2(execErr);
340
+ } else {
333
341
  resolve4({ stdout, stderr });
342
+ }
334
343
  });
335
344
  });
336
345
  }
@@ -392,7 +401,7 @@ function looksLikeRowData(val) {
392
401
  }
393
402
  }
394
403
  function stripAnsi(text) {
395
- return text.replace(/\x1b\[[0-9;]*m/g, "");
404
+ return text.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
396
405
  }
397
406
  function parseAsciiTable(text) {
398
407
  const cleaned = stripAnsi(text);
@@ -439,41 +448,43 @@ async function execDbtShow(sql, limit) {
439
448
  const args = ["show", "--inline", sql, "--output", "json", "--log-format", "json"];
440
449
  if (limit !== undefined)
441
450
  args.push("--limit", String(limit));
442
- let lines;
451
+ let primaryRunError;
452
+ let lines = [];
443
453
  try {
444
454
  const { stdout } = await run2(args);
445
455
  lines = parseJsonLines(stdout);
446
- } catch {
447
- lines = [];
448
- }
449
- const previewLine = lines.find((l) => l.data?.preview) ?? lines.find((l) => l.data?.rows) ?? lines.find((l) => l.result?.preview) ?? lines.find((l) => l.result?.rows);
450
- const sqlLine = lines.find((l) => l.data?.sql) ?? lines.find((l) => l.data?.compiled_sql) ?? lines.find((l) => l.result?.sql);
451
- if (previewLine) {
452
- const preview = previewLine.data?.preview ?? previewLine.data?.rows ?? previewLine.result?.preview ?? previewLine.result?.rows;
453
- let rows;
454
- if (typeof preview === "string") {
455
- const parsed = safeJsonParse(preview);
456
- if (Array.isArray(parsed)) {
457
- rows = parsed;
456
+ } catch (e) {
457
+ primaryRunError = toExecFileError(e);
458
+ }
459
+ if (!primaryRunError) {
460
+ const previewLine = lines.find((l) => l.data?.preview) ?? lines.find((l) => l.data?.rows) ?? lines.find((l) => l.result?.preview) ?? lines.find((l) => l.result?.rows);
461
+ const sqlLine = lines.find((l) => l.data?.sql) ?? lines.find((l) => l.data?.compiled_sql) ?? lines.find((l) => l.result?.sql);
462
+ if (previewLine) {
463
+ const preview = previewLine.data?.preview ?? previewLine.data?.rows ?? previewLine.result?.preview ?? previewLine.result?.rows;
464
+ let rows;
465
+ if (typeof preview === "string") {
466
+ const parsed = safeJsonParse(preview);
467
+ rows = Array.isArray(parsed) ? parsed : [];
468
+ } else if (Array.isArray(preview)) {
469
+ rows = preview;
458
470
  } else {
459
471
  rows = [];
460
472
  }
461
- } else {
462
- rows = preview;
463
- }
464
- const columnNames = rows.length > 0 && rows[0] ? Object.keys(rows[0]) : [];
465
- const compiledSql = sqlLine?.data?.sql ?? sqlLine?.data?.compiled_sql ?? sqlLine?.result?.sql ?? sql;
466
- return { columnNames, columnTypes: columnNames.map(() => "string"), data: rows, rawSql: sql, compiledSql };
467
- }
468
- for (const line of lines) {
469
- const found = deepFind(line, (val) => looksLikeRowData(val));
470
- if (found) {
471
- const rows = typeof found === "string" ? JSON.parse(found) : found;
472
473
  const columnNames = rows.length > 0 && rows[0] ? Object.keys(rows[0]) : [];
473
- const compiledSql = deepFind(line, (val) => looksLikeSql(val)) ?? sql;
474
+ const compiledSql = sqlLine?.data?.sql ?? sqlLine?.data?.compiled_sql ?? sqlLine?.result?.sql ?? sql;
474
475
  return { columnNames, columnTypes: columnNames.map(() => "string"), data: rows, rawSql: sql, compiledSql };
475
476
  }
477
+ for (const line of lines) {
478
+ const found = deepFind(line, (val) => looksLikeRowData(val));
479
+ if (found) {
480
+ const rows = typeof found === "string" ? JSON.parse(found) : found;
481
+ const columnNames = rows.length > 0 && rows[0] ? Object.keys(rows[0]) : [];
482
+ const compiledSql = deepFind(line, (val) => looksLikeSql(val)) ?? sql;
483
+ return { columnNames, columnTypes: columnNames.map(() => "string"), data: rows, rawSql: sql, compiledSql };
484
+ }
485
+ }
476
486
  }
487
+ let plainRunError;
477
488
  try {
478
489
  const plainArgs = ["show", "--inline", sql];
479
490
  if (limit !== undefined)
@@ -489,62 +500,135 @@ async function execDbtShow(sql, limit) {
489
500
  compiledSql: sql
490
501
  };
491
502
  }
492
- } catch {}
503
+ } catch (e) {
504
+ plainRunError = toExecFileError(e);
505
+ }
506
+ if (primaryRunError) {
507
+ const errorLogLines = parseJsonLines(primaryRunError.stdout?.toString() ?? "");
508
+ const realError = extractDbtError(errorLogLines, primaryRunError, plainRunError);
509
+ if (realError) {
510
+ const hasDbtCategoryPrefix = /^(Compilation|Database|Runtime|Parsing|Validation|Dependency)\s+Error\b/.test(realError);
511
+ throw new Error(hasDbtCategoryPrefix ? realError : `dbt show failed: ${realError}`);
512
+ }
513
+ }
514
+ if (plainRunError) {
515
+ const fallback = extractDbtError([], undefined, plainRunError) ?? fallbackExitMessage(undefined, plainRunError) ?? "unknown error";
516
+ throw new Error(`Could not parse dbt show JSON output, and plain-text fallback failed: ${fallback}`);
517
+ }
493
518
  throw new Error("Could not parse dbt show output in any format (JSON, heuristic, or plain text). " + `Got ${lines.length} JSON lines.`);
494
519
  }
520
+ function extractDbtError(lines, primary, plain) {
521
+ if (!primary && !plain)
522
+ return;
523
+ const errorMessages = lines.map((l) => {
524
+ const line = l;
525
+ const isError = line.info?.level === "error" || line.level === "error";
526
+ if (!isError)
527
+ return;
528
+ return line.info?.msg ?? line.msg;
529
+ }).filter((m) => typeof m === "string" && m.trim().length > 0);
530
+ const structuredMsg = errorMessages.at(-1);
531
+ const primaryStderr = primary?.stderr?.toString().trim();
532
+ const plainStderr = plain?.stderr?.toString().trim();
533
+ const chosen = (structuredMsg && structuredMsg.length > 0 ? structuredMsg : undefined) ?? (primaryStderr && primaryStderr.length > 0 ? primaryStderr : undefined) ?? (plainStderr && plainStderr.length > 0 ? plainStderr : undefined) ?? fallbackExitMessage(primary, plain);
534
+ return chosen ? stripAnsi(chosen) : undefined;
535
+ }
536
+ function fallbackExitMessage(primary, plain) {
537
+ const err = primary ?? plain;
538
+ if (!err)
539
+ return;
540
+ const looksLikeCommandFailed = typeof err.message === "string" && err.message.startsWith("Command failed:");
541
+ if (!looksLikeCommandFailed)
542
+ return err.message;
543
+ if (typeof err.code === "number")
544
+ return `dbt exited with status ${err.code}`;
545
+ if (err.signal)
546
+ return `dbt killed by signal ${err.signal}`;
547
+ if (typeof err.code === "string")
548
+ return `dbt failed: ${err.code}`;
549
+ return "dbt failed (no exit code reported)";
550
+ }
551
+ function bubbleDbtError(label, primary, plain) {
552
+ const errorLogLines = primary?.stdout ? parseJsonLines(primary.stdout.toString()) : [];
553
+ const real = extractDbtError(errorLogLines, primary, plain);
554
+ if (real) {
555
+ const hasDbtCategoryPrefix = /^(Compilation|Database|Runtime|Parsing|Validation|Dependency)\s+Error\b/.test(real);
556
+ return hasDbtCategoryPrefix ? real : `${label}: ${real}`;
557
+ }
558
+ return `${label}: ${fallbackExitMessage(primary, plain) ?? "unknown error"}`;
559
+ }
495
560
  async function execDbtCompile(model) {
496
561
  const args = ["compile", "--select", model, "--output", "json", "--log-format", "json"];
497
- let lines;
562
+ let lines = [];
563
+ let primaryRunError;
498
564
  try {
499
565
  const { stdout } = await run2(args);
500
566
  lines = parseJsonLines(stdout);
501
- } catch {
502
- lines = [];
567
+ } catch (e) {
568
+ primaryRunError = toExecFileError(e);
503
569
  }
504
- const sql = findCompiledSql(lines);
505
- if (sql)
506
- return { sql };
507
- for (const line of lines) {
508
- const found = deepFind(line, (val) => looksLikeSql(val));
509
- if (found)
510
- return { sql: found };
570
+ if (!primaryRunError) {
571
+ const sql = findCompiledSql(lines);
572
+ if (sql)
573
+ return { sql };
574
+ for (const line of lines) {
575
+ const found = deepFind(line, (val) => looksLikeSql(val));
576
+ if (found)
577
+ return { sql: found };
578
+ }
511
579
  }
580
+ let manifestRunError;
512
581
  try {
513
582
  await run2(["compile", "--select", model]);
514
- } catch {}
583
+ } catch (e) {
584
+ manifestRunError = toExecFileError(e);
585
+ }
515
586
  const fromManifest = readCompiledFromManifest(model);
516
587
  if (fromManifest)
517
588
  return { sql: fromManifest };
589
+ let plainRunError;
518
590
  try {
519
591
  const { stdout: plainOut } = await run2(["compile", "--select", model]);
520
592
  return { sql: plainOut.trim() };
521
593
  } catch (e) {
522
- throw new Error(`Could not compile model '${model}' in any format (JSON, heuristic, or manifest). ` + `Last error: ${e instanceof Error ? e.message : String(e)}`);
594
+ plainRunError = toExecFileError(e);
595
+ }
596
+ if (primaryRunError || plainRunError || manifestRunError) {
597
+ throw new Error(bubbleDbtError("dbt compile failed", primaryRunError, plainRunError ?? manifestRunError));
523
598
  }
599
+ throw new Error(`Could not compile model '${model}' in any format (JSON, heuristic, or manifest).`);
524
600
  }
525
601
  async function execDbtCompileInline(sql, _model) {
526
602
  const args = ["compile", "--inline", sql, "--output", "json", "--log-format", "json"];
527
- let lines;
603
+ let lines = [];
604
+ let primaryRunError;
528
605
  try {
529
606
  const { stdout } = await run2(args);
530
607
  lines = parseJsonLines(stdout);
531
- } catch {
532
- lines = [];
608
+ } catch (e) {
609
+ primaryRunError = toExecFileError(e);
533
610
  }
534
- const compiled = findCompiledSql(lines);
535
- if (compiled)
536
- return { sql: compiled };
537
- for (const line of lines) {
538
- const found = deepFind(line, (val) => looksLikeSql(val));
539
- if (found)
540
- return { sql: found };
611
+ if (!primaryRunError) {
612
+ const compiled = findCompiledSql(lines);
613
+ if (compiled)
614
+ return { sql: compiled };
615
+ for (const line of lines) {
616
+ const found = deepFind(line, (val) => looksLikeSql(val));
617
+ if (found)
618
+ return { sql: found };
619
+ }
541
620
  }
621
+ let plainRunError;
542
622
  try {
543
623
  const { stdout: plainOut } = await run2(["compile", "--inline", sql]);
544
624
  return { sql: plainOut.trim() };
545
625
  } catch (e) {
546
- throw new Error(`Could not compile inline SQL in any format (JSON, heuristic, or plain text). ` + `Last error: ${e instanceof Error ? e.message : String(e)}`);
626
+ plainRunError = toExecFileError(e);
627
+ }
628
+ if (primaryRunError || plainRunError) {
629
+ throw new Error(bubbleDbtError("dbt compile inline failed", primaryRunError, plainRunError));
547
630
  }
631
+ throw new Error("Could not compile inline SQL in any format (JSON, heuristic, or plain text).");
548
632
  }
549
633
  function findCompiledSql(lines) {
550
634
  const compiledLine = lines.find((l) => l.data?.compiled) ?? lines.find((l) => l.data?.compiled_code) ?? lines.find((l) => l.result?.node?.compiled_code) ?? lines.find((l) => l.result?.compiled_code) ?? lines.find((l) => l.data?.compiled_sql);
package/package.json CHANGED
@@ -14,20 +14,20 @@
14
14
  "scripts": {
15
15
  "postinstall": "bun ./postinstall.mjs || node ./postinstall.mjs"
16
16
  },
17
- "version": "0.8.7",
17
+ "version": "0.8.9",
18
18
  "license": "MIT",
19
19
  "dependencies": {
20
20
  "@altimateai/altimate-core": "0.5.1"
21
21
  },
22
22
  "optionalDependencies": {
23
- "@altimateai/altimate-code-linux-x64-baseline": "0.8.7",
24
- "@altimateai/altimate-code-windows-x64": "0.8.7",
25
- "@altimateai/altimate-code-linux-x64": "0.8.7",
26
- "@altimateai/altimate-code-darwin-x64": "0.8.7",
27
- "@altimateai/altimate-code-darwin-x64-baseline": "0.8.7",
28
- "@altimateai/altimate-code-darwin-arm64": "0.8.7",
29
- "@altimateai/altimate-code-windows-x64-baseline": "0.8.7",
30
- "@altimateai/altimate-code-linux-arm64": "0.8.7"
23
+ "@altimateai/altimate-code-linux-x64-baseline": "0.8.9",
24
+ "@altimateai/altimate-code-windows-x64": "0.8.9",
25
+ "@altimateai/altimate-code-linux-x64": "0.8.9",
26
+ "@altimateai/altimate-code-darwin-x64": "0.8.9",
27
+ "@altimateai/altimate-code-darwin-x64-baseline": "0.8.9",
28
+ "@altimateai/altimate-code-darwin-arm64": "0.8.9",
29
+ "@altimateai/altimate-code-windows-x64-baseline": "0.8.9",
30
+ "@altimateai/altimate-code-linux-arm64": "0.8.9"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "pg": ">=8",