Skip to content

feat: dbt_project_health tool — reuse altimate-core dbt health checks - #1030

Open
mdesmet wants to merge 2 commits into
mainfrom
feat/dbt-project-health-tool
Open

feat: dbt_project_health tool — reuse altimate-core dbt health checks#1030
mdesmet wants to merge 2 commits into
mainfrom
feat/dbt-project-health-tool

Conversation

@mdesmet

@mdesmet mdesmet commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Wires the new Rust dbt health checks (altimate_core::dbt::health, added in AltimateAI/altimate-core-internal#762) into altimate-code, so dbt governance / modelling / documentation / test checks run directly against a dbt project's files — no compiled manifest.json required — through the @altimateai/altimate-core napi binding.

Changes

  • Bridge + dispatcher: new altimate_core.dbt_project_health bridge-method type and a Dispatcher.register handler calling core.dbtProjectHealth(projectDir, configJson?, catalogPath?).
  • Tool: DbtProjectHealthTool (dbt_project_health) — takes a project_dir (+ optional config_path, catalog_path), runs the checks, and formats findings grouped by severity. Registered in the tool registry and the altimate barrel export.
  • Dep: bumps @altimateai/altimate-core 0.5.1 → 0.8.0 (the version that exports dbtProjectHealth).

Dependency

Requires @altimateai/altimate-core 0.8.0, published from AltimateAI/altimate-core-internal#762 (stacked on #761). Merge/release that first.

Verification

TypeScript typecheck is clean on all changed files against the 0.8.0 core types (verified by overlaying the freshly-built core index.d.ts). End-to-end, the underlying core returns findings for a sample project (25 findings across 13 check codes on the fixture).

🤖 Generated with Claude Code


Summary by cubic

Adds dbt project health checks that run directly on project files and a deterministic config inference flow, powered by @altimateai/altimate-core 0.8.0. You can now generate a per-project .altimate/dbt-health.yml and have the health tool pick it up automatically.

  • New Features

    • Bridge + dispatcher for altimate_core.dbt_project_health and altimate_core.dbt_health_infer_config.
    • dbt_project_health tool: runs checks on files; accepts project_dir with optional config_path/catalog_path; auto-discovers .altimate/dbt-health.{yml,yaml,json}; outputs findings with severity counts.
    • dbt_health_config tool: infers and writes .altimate/dbt-health.yml per project; adds /dbt-health-config command to infer then run health; tools registered and exported.
  • Dependencies

    • Bumps @altimateai/altimate-core from 0.5.1 to 0.8.0.
    • Requires @altimateai/altimate-core 0.8.0 to be published first.

Written for commit fefe41f. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added dbt project health tooling to analyze projects and report findings, with support for optional configuration and catalog inputs.
    • Findings are normalized, sorted by severity and code, and summarized with per-severity counts; each finding includes messages and recommendations when available (including file/identifier context when present).
    • Added a dbt health-config capability and command to infer and write .altimate/dbt-health.yml, returning the written path(s) and resulting severity counts.

Wires the new `altimate_core::dbt::health` checks (altimate-core-internal, requires
@altimateai/altimate-core 0.8.0) into altimate-code:

- Bridge method type `altimate_core.dbt_project_health` + Dispatcher registration
  calling core.dbtProjectHealth.
- New DbtProjectHealthTool: runs dbt health checks directly against a dbt project
  directory (no manifest required), optional config + catalog paths, formats findings
  grouped by severity.
- Registers the tool in the tool registry and altimate barrel export; bumps the
  @altimateai/altimate-core dependency 0.5.1 -> 0.8.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@github-actions

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds built-in dbt_health_config and dbt_project_health tools backed by Altimate Core, including configuration inference, optional file writing, finding normalization, severity counts, formatted output, registry exposure, and command documentation.

Changes

dbt health tooling

Layer / File(s) Summary
Native health bridge contracts and handlers
packages/opencode/package.json, packages/opencode/src/altimate/native/types.ts, packages/opencode/src/altimate/native/altimate-core.ts
Updates Altimate Core and adds typed native handlers for dbt project health evaluation and health configuration inference.
Health configuration inference
packages/opencode/src/altimate/tools/dbt-health-config.ts
Adds configuration inference with optional writing to .altimate/dbt-health.yml, check counting, metadata, and error handling.
Project health evaluation and formatting
packages/opencode/src/altimate/tools/dbt-project-health.ts
Adds config discovery, native evaluation, severity sorting, counts, failure handling, and formatted findings output.
Tool exports, registry, and command wiring
packages/opencode/src/altimate/index.ts, packages/opencode/src/tool/registry.ts, .opencode/command/dbt-health-config.md
Exports and registers both tools and documents the workflow for generating configurations and reporting findings.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Command
  participant DbtHealthConfigTool
  participant DbtProjectHealthTool
  participant Dispatcher
  participant AltimateCore
  Command->>DbtHealthConfigTool: Infer and write dbt health config
  DbtHealthConfigTool->>Dispatcher: Call altimate_core.dbt_health_infer_config
  Dispatcher->>AltimateCore: Send project directory
  AltimateCore-->>Dispatcher: Return inferred YAML
  DbtHealthConfigTool-->>Command: Return written path and check count
  Command->>DbtProjectHealthTool: Evaluate project health
  DbtProjectHealthTool->>Dispatcher: Call altimate_core.dbt_project_health
  Dispatcher->>AltimateCore: Send project, config, and catalog paths
  AltimateCore-->>Dispatcher: Return JSON findings
  DbtProjectHealthTool-->>Command: Return sorted findings and severity counts
Loading

Possibly related PRs

Poem

A rabbit writes rules in a YAML burrow,
Then checks dbt findings in tidy array rows.
Errors hop first, warnings follow behind,
Core sends the health report the tools unwind.
Hip-hop—config and health now shine!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the changes and verification, but it omits required template sections like Issue, Type of change, and Checklist. Add the missing template sections, especially Issue for this PR, Type of change, Screenshots/recordings if relevant, and the Checklist items.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding the dbt_project_health tool backed by altimate-core health checks.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dbt-project-health-tool

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/opencode/src/altimate/tools/dbt-project-health.ts`:
- Around line 37-40: Update the config_path handling in the dbt project health
tool so an explicitly supplied configuration file that is missing or unreadable
produces a clear tool error instead of falling back to default checks. Validate
file existence and propagate read failures, while preserving the current
behavior when config_path is not provided.
- Around line 57-63: Update the findings handling in the dbt project health flow
to validate result.data.findings with a runtime schema before copying or sorting
it. Reject malformed or missing payloads by returning the existing error
response, while preserving the current severity and code ordering for valid
HealthFinding arrays.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 71c27c6d-b7a3-4fad-9d71-6ce242129103

📥 Commits

Reviewing files that changed from the base of the PR and between 537d3b7 and 9b198bd.

📒 Files selected for processing (6)
  • packages/opencode/package.json
  • packages/opencode/src/altimate/index.ts
  • packages/opencode/src/altimate/native/altimate-core.ts
  • packages/opencode/src/altimate/native/types.ts
  • packages/opencode/src/altimate/tools/dbt-project-health.ts
  • packages/opencode/src/tool/registry.ts

Comment on lines +37 to +40
if (args.config_path) {
const file = Bun.file(args.config_path)
if (await file.exists()) config_json = await file.text()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not silently ignore an explicitly supplied config file.

When config_path is set but the file is missing or unreadable, the tool runs with default checks and reports results as if the requested configuration was applied. Return a clear tool error instead.

Suggested handling
 if (args.config_path) {
-  const file = Bun.file(args.config_path)
-  if (await file.exists()) config_json = await file.text()
+  try {
+    config_json = await Bun.file(args.config_path).text()
+  } catch (error) {
+    const message = error instanceof Error ? error.message : String(error)
+    return {
+      title: "dbt health: ERROR",
+      metadata: { error: message },
+      output: `Failed to read dbt health config: ${message}`,
+    }
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (args.config_path) {
const file = Bun.file(args.config_path)
if (await file.exists()) config_json = await file.text()
}
if (args.config_path) {
try {
config_json = await Bun.file(args.config_path).text()
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
return {
title: "dbt health: ERROR",
metadata: { error: message },
output: `Failed to read dbt health config: ${message}`,
}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/src/altimate/tools/dbt-project-health.ts` around lines 37 -
40, Update the config_path handling in the dbt project health tool so an
explicitly supplied configuration file that is missing or unreadable produces a
clear tool error instead of falling back to default checks. Validate file
existence and propagate read failures, while preserving the current behavior
when config_path is not provided.

Comment on lines +57 to +63
const findings = ((result.data.findings as HealthFinding[] | undefined) ?? []).slice()
findings.sort(
(a, b) => (SEVERITY_ORDER[a.severity] ?? 3) - (SEVERITY_ORDER[b.severity] ?? 3) || a.code.localeCompare(b.code),
)

const counts = { error: 0, warning: 0, info: 0 } as Record<string, number>
for (const f of findings) counts[f.severity] = (counts[f.severity] ?? 0) + 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' packages/opencode/src/altimate/tools/dbt-project-health.ts

printf '\n---- search HealthFinding ----\n'
rg -n "HealthFinding|findings" packages/opencode/src/altimate -g '!**/dist/**' -g '!**/build/**'

Repository: AltimateAI/altimate-code

Length of output: 27057


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- file ---\n'
nl -ba packages/opencode/src/altimate/tools/dbt-project-health.ts | sed -n '1,220p'

printf '\n--- search definitions ---\n'
rg -n "type HealthFinding|interface HealthFinding|Schema.*HealthFinding|findings:" packages/opencode/src/altimate -g '!**/dist/**' -g '!**/build/**'

Repository: AltimateAI/altimate-code

Length of output: 253


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- altimate-core dbt_project_health references ---'
rg -n "dbt_project_health|HealthFinding|JSON.parse\\(json\\)" packages/opencode/src/altimate/native/altimate-core.ts packages/opencode/src/altimate/native -g '!**/dist/**' -g '!**/build/**'

printf '\n%s\n' '--- dispatcher definition ---'
rg -n "class Dispatcher|export const Dispatcher|call\\(" packages/opencode/src/altimate/native packages/opencode/src/altimate -g '!**/dist/**' -g '!**/build/**'

Repository: AltimateAI/altimate-code

Length of output: 14037


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- native altimate_core.dbt_project_health implementation ---'
nl -ba packages/opencode/src/altimate/native/altimate-core.ts | sed -n '260,285p'

printf '\n%s\n' '--- dispatcher return typing ---'
nl -ba packages/opencode/src/altimate/native/types.ts | sed -n '1238,1260p'

printf '\n%s\n' '--- dispatcher implementation ---'
rg -n "class Dispatcher|export class Dispatcher|static async call|async call" packages/opencode/src/altimate/native packages/opencode/src/altimate -g '!**/dist/**' -g '!**/build/**'

Repository: AltimateAI/altimate-code

Length of output: 265


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- native altimate_core.dbt_project_health implementation ---'
nl -ba packages/opencode/src/altimate/native/altimate-core.ts | sed -n '260,285p'

printf '%s\n' '--- dispatcher return typing ---'
nl -ba packages/opencode/src/altimate/native/types.ts | sed -n '1238,1260p'

printf '%s\n' '--- dispatcher implementation ---'
rg -n "class Dispatcher|export class Dispatcher|static async call|async call" packages/opencode/src/altimate/native packages/opencode/src/altimate -g '!**/dist/**' -g '!**/build/**'

Repository: AltimateAI/altimate-code

Length of output: 265


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- native altimate_core.dbt_project_health implementation ---'
sed -n '268,278p' packages/opencode/src/altimate/native/altimate-core.ts | cat -n

printf '\n%s\n' '--- native types entry ---'
sed -n '1242,1254p' packages/opencode/src/altimate/native/types.ts | cat -n

printf '\n%s\n' '--- dispatcher implementation files ---'
fd -a "dispatcher*.ts" packages/opencode/src/altimate packages/opencode/src -E dist -E build

Repository: AltimateAI/altimate-code

Length of output: 1545


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- dispatcher.ts ---'
sed -n '1,260p' packages/opencode/src/altimate/native/dispatcher.ts | cat -n

printf '\n%s\n' '--- AltimateCoreResult definition ---'
rg -n "type AltimateCoreResult|interface AltimateCoreResult|export type AltimateCoreResult" packages/opencode/src/altimate/native/types.ts packages/opencode/src/altimate/native -g '!**/dist/**' -g '!**/build/**'

Repository: AltimateAI/altimate-code

Length of output: 3918


Validate the native findings payload at runtime. result.data.findings is only typed here; a malformed or missing bridge payload can throw on .slice()/sort() or be treated as zero findings. Parse it with a runtime schema and return an error on invalid data.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/src/altimate/tools/dbt-project-health.ts` around lines 57 -
63, Update the findings handling in the dbt project health flow to validate
result.data.findings with a runtime schema before copying or sorting it. Reject
malformed or missing payloads by returning the existing error response, while
preserving the current severity and code ordering for valid HealthFinding
arrays.

Source: Coding guidelines

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/opencode/src/altimate/tools/dbt-project-health.ts">

<violation number="1" location="packages/opencode/src/altimate/tools/dbt-project-health.ts:39">
P2: A misspelled or unavailable `config_path` silently runs default health checks and can report a clean result despite requested disabled checks or severity overrides not being applied. Return an explicit tool error when the config cannot be read instead of omitting it.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

let config_json: string | undefined
if (args.config_path) {
const file = Bun.file(args.config_path)
if (await file.exists()) config_json = await file.text()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: A misspelled or unavailable config_path silently runs default health checks and can report a clean result despite requested disabled checks or severity overrides not being applied. Return an explicit tool error when the config cannot be read instead of omitting it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/altimate/tools/dbt-project-health.ts, line 39:

<comment>A misspelled or unavailable `config_path` silently runs default health checks and can report a clean result despite requested disabled checks or severity overrides not being applied. Return an explicit tool error when the config cannot be read instead of omitting it.</comment>

<file context>
@@ -0,0 +1,88 @@
+    let config_json: string | undefined
+    if (args.config_path) {
+      const file = Bun.file(args.config_path)
+      if (await file.exists()) config_json = await file.text()
+    }
+
</file context>

…project config)

Adds a deterministic config-inference flow for the dbt health checks:

- `dbt_health_config` tool: calls the new core `dbtHealthInferConfig` (via the dispatcher)
  and writes the inferred config to <project_dir>/.altimate/dbt-health.yml (per-project, so
  multiple dbt projects each get their own file). Requires @altimateai/altimate-core 0.8.0.
- `dbt_project_health` now auto-discovers <project_dir>/.altimate/dbt-health.{yml,yaml,json}
  when no explicit config_path is passed, so the inferred config is used automatically.
- `/dbt-health-config` slash command (.opencode/command): infers + writes the config for
  each dbt project in the worktree, then runs the health checks to show the effect.
- Bridge type + dispatcher registration for altimate_core.dbt_health_infer_config.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/opencode/src/altimate/tools/dbt-health-config.ts`:
- Around line 39-40: Update the output-writing flow around mkdir and Bun.write
to prevent symlink escapes outside project_dir. Use the existing symlink-aware
containsReal check on the final output path and reject any symlinked directory
or file components before writing, or use an equivalent atomic no-follow file
creation approach; preserve normal writes for safe paths.
- Line 35: Update the output-path construction around outPath to use node:path’s
join with args.project_dir and DBT_HEALTH_CONFIG_RELPATH. Remove the manual
trailing-slash trimming and string concatenation so path separators are
normalized consistently across platforms.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 25a9ad29-0e52-48e6-ab98-5954341588fe

📥 Commits

Reviewing files that changed from the base of the PR and between 9b198bd and fefe41f.

📒 Files selected for processing (7)
  • .opencode/command/dbt-health-config.md
  • packages/opencode/src/altimate/index.ts
  • packages/opencode/src/altimate/native/altimate-core.ts
  • packages/opencode/src/altimate/native/types.ts
  • packages/opencode/src/altimate/tools/dbt-health-config.ts
  • packages/opencode/src/altimate/tools/dbt-project-health.ts
  • packages/opencode/src/tool/registry.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/opencode/src/altimate/index.ts
  • packages/opencode/src/tool/registry.ts
  • packages/opencode/src/altimate/native/altimate-core.ts
  • packages/opencode/src/altimate/tools/dbt-project-health.ts

}

const yaml = String((result.data as { config?: unknown }).config ?? "")
const outPath = `${args.project_dir.replace(/\/+$/, "")}/${DBT_HEALTH_CONFIG_RELPATH}`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- file outline ---'
ast-grep outline packages/opencode/src/altimate/tools/dbt-health-config.ts --view expanded || true

echo '--- relevant lines ---'
sed -n '1,120p' packages/opencode/src/altimate/tools/dbt-health-config.ts

echo '--- path semantics probe ---'
node - <<'JS'
const path = require('node:path');

const rel = 'dbt_health_config.yml';
const inputs = [
  '/',
  '/tmp/',
  'C:/',
  'C:/work/',
  'C:\\',
  'C:\\work\\',
  'relative/',
];

for (const p of inputs) {
  const stripped = `${p.replace(/\/+$/, '')}/${rel}`;
  const joined = path.join(p, rel);
  const posixJoined = path.posix.join(p, rel);
  const winJoined = path.win32.join(p, rel);
  console.log(JSON.stringify({ p, stripped, joined, posixJoined, winJoined }));
}
JS

Repository: AltimateAI/altimate-code

Length of output: 4011


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,120p' packages/opencode/src/altimate/tools/dbt-health-config.ts
node - <<'JS'
const path = require('node:path');
const rel = 'dbt_health_config.yml';
for (const p of ['/', '/tmp/', 'C:/', 'C:/work/', 'C:\\', 'C:\\work\\', 'relative/']) {
  const stripped = `${p.replace(/\/+$/, '')}/${rel}`;
  const joined = path.join(p, rel);
  console.log(JSON.stringify({ p, stripped, joined }));
}
JS

Repository: AltimateAI/altimate-code

Length of output: 3259


🏁 Script executed:

#!/bin/bash
set -euo pipefail
node - <<'JS'
const path = require('node:path');

const samples = [
  '/.altimate/dbt-health.yml',
  'C:/.altimate/dbt-health.yml',
  'C:\\/.altimate/dbt-health.yml',
  'C:/work/.altimate/dbt-health.yml',
  'C:\\work\\/.altimate/dbt-health.yml',
  '//server/share/.altimate/dbt-health.yml',
];

for (const s of samples) {
  console.log(JSON.stringify({
    s,
    posix_dirname: path.posix.dirname(s),
    win_dirname: path.win32.dirname(s),
    posix_isAbs: path.posix.isAbsolute(s),
    win_isAbs: path.win32.isAbsolute(s),
    normalized_win: path.win32.normalize(s),
  }));
}
JS

Repository: AltimateAI/altimate-code

Length of output: 1352


Build the output path with node:path. Manual trimming plus string concatenation can leave mixed separators on Windows paths; join(args.project_dir, DBT_HEALTH_CONFIG_RELPATH) normalizes it consistently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/src/altimate/tools/dbt-health-config.ts` at line 35, Update
the output-path construction around outPath to use node:path’s join with
args.project_dir and DBT_HEALTH_CONFIG_RELPATH. Remove the manual trailing-slash
trimming and string concatenation so path separators are normalized consistently
across platforms.

Comment on lines +39 to +40
await mkdir(dirname(outPath), { recursive: true })
await Bun.write(outPath, yaml)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File outline ==\n'
ast-grep outline packages/opencode/src/altimate/tools/dbt-health-config.ts --view expanded || true

printf '\n== File contents (relevant slice) ==\n'
wc -l packages/opencode/src/altimate/tools/dbt-health-config.ts
sed -n '1,220p' packages/opencode/src/altimate/tools/dbt-health-config.ts | cat -n

printf '\n== Search for related path / symlink handling ==\n'
rg -n "symlink|realpath|canonical|containment|mkdir\\(|Bun\\.write\\(|dbt-health.yml|\\.altimate|project_dir|projectDir|outPath" packages/opencode/src -S

Repository: AltimateAI/altimate-code

Length of output: 41320


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== util/filesystem.ts excerpt ==\n'
sed -n '200,280p' packages/opencode/src/util/filesystem.ts | cat -n

printf '\n== write helpers / containment usages ==\n'
rg -n "containsReal|containsPath|mkdir\\(|writeFile\\(|Bun\\.write\\(" packages/opencode/src/util packages/opencode/src/altimate packages/opencode/src/cli -S

Repository: AltimateAI/altimate-code

Length of output: 6872


Prevent symlink escapes in the write path. mkdir(..., { recursive: true }) plus Bun.write(outPath, yaml) can still follow a pre-existing .altimate directory or dbt-health.yml symlink and write outside project_dir. Reuse the symlink-aware containment check (containsReal) on the final path and reject symlinked output components, or create the file atomically with no-follow semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/src/altimate/tools/dbt-health-config.ts` around lines 39 -
40, Update the output-writing flow around mkdir and Bun.write to prevent symlink
escapes outside project_dir. Use the existing symlink-aware containsReal check
on the final output path and reject any symlinked directory or file components
before writing, or use an equivalent atomic no-follow file creation approach;
preserve normal writes for safe paths.

Source: Coding guidelines

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/opencode/src/altimate/tools/dbt-health-config.ts">

<violation number="1" location="packages/opencode/src/altimate/tools/dbt-health-config.ts:35">
P3: The output path is built with manual slash-trimming and template concatenation, but `dirname` from `node:path` is already imported. Using `join(args.project_dir, DBT_HEALTH_CONFIG_RELPATH)` would normalize separators consistently (particularly relevant on Windows) and is more idiomatic given the existing import.</violation>

<violation number="2" location="packages/opencode/src/altimate/tools/dbt-health-config.ts:39">
P2: The `mkdir` + `Bun.write` sequence will follow pre-existing symlinks. If `.altimate/` or `dbt-health.yml` is a symlink pointing outside `project_dir`, this writes attacker-controlled content to an arbitrary path. Consider resolving `outPath` after directory creation and verifying it still resides within `project_dir` (e.g., via a realpath containment check) before writing.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


let written = false
if (args.write !== false) {
await mkdir(dirname(outPath), { recursive: true })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The mkdir + Bun.write sequence will follow pre-existing symlinks. If .altimate/ or dbt-health.yml is a symlink pointing outside project_dir, this writes attacker-controlled content to an arbitrary path. Consider resolving outPath after directory creation and verifying it still resides within project_dir (e.g., via a realpath containment check) before writing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/altimate/tools/dbt-health-config.ts, line 39:

<comment>The `mkdir` + `Bun.write` sequence will follow pre-existing symlinks. If `.altimate/` or `dbt-health.yml` is a symlink pointing outside `project_dir`, this writes attacker-controlled content to an arbitrary path. Consider resolving `outPath` after directory creation and verifying it still resides within `project_dir` (e.g., via a realpath containment check) before writing.</comment>

<file context>
@@ -0,0 +1,55 @@
+
+    let written = false
+    if (args.write !== false) {
+      await mkdir(dirname(outPath), { recursive: true })
+      await Bun.write(outPath, yaml)
+      written = true
</file context>

}

const yaml = String((result.data as { config?: unknown }).config ?? "")
const outPath = `${args.project_dir.replace(/\/+$/, "")}/${DBT_HEALTH_CONFIG_RELPATH}`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The output path is built with manual slash-trimming and template concatenation, but dirname from node:path is already imported. Using join(args.project_dir, DBT_HEALTH_CONFIG_RELPATH) would normalize separators consistently (particularly relevant on Windows) and is more idiomatic given the existing import.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/altimate/tools/dbt-health-config.ts, line 35:

<comment>The output path is built with manual slash-trimming and template concatenation, but `dirname` from `node:path` is already imported. Using `join(args.project_dir, DBT_HEALTH_CONFIG_RELPATH)` would normalize separators consistently (particularly relevant on Windows) and is more idiomatic given the existing import.</comment>

<file context>
@@ -0,0 +1,55 @@
+    }
+
+    const yaml = String((result.data as { config?: unknown }).config ?? "")
+    const outPath = `${args.project_dir.replace(/\/+$/, "")}/${DBT_HEALTH_CONFIG_RELPATH}`
+
+    let written = false
</file context>

@dev-punia-altimate dev-punia-altimate left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Code Review — OpenCodeReview (Gemini) — 5 finding(s)

  • 5 anchored to a line (posted inline when the comment stream is on)
  • 0 without a line anchor
All findings (full text)

1. packages/opencode/src/altimate/tools/dbt-project-health.ts (L38-L52)

[🟠 MEDIUM] If args.config_path is explicitly provided but the file does not exist, this logic will silently proceed with config_json = undefined. It is recommended to throw an error or return an error response when an explicitly provided configuration file is not found, to avoid running health checks with unintended settings.

Suggested change:

    let config_json: string | undefined
    if (args.config_path) {
      const file = Bun.file(args.config_path)
      if (!(await file.exists())) {
        return {
          title: "dbt health: ERROR",
          metadata: { error: `Config file not found: ${args.config_path}` },
          output: `Failed to find config file at ${args.config_path}`,
        }
      }
      config_json = await file.text()
    } else {
      const candidates = [
        `${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.yml`,
        `${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.yaml`,
        `${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.json`,
      ]
      for (const candidate of candidates) {
        const file = Bun.file(candidate)
        if (await file.exists()) {
          config_json = await file.text()
          break
        }
      }
    }

2. packages/opencode/src/altimate/tools/dbt-project-health.ts (L93-L95)

[🔵 LOW] According to the review checklist, nested ternary expressions are not allowed. Please refactor this to use an if...else statement or logical operators to improve readability.

Suggested change:

  for (const f of findings) {
    let where = ""
    if (f.file) {
      where = ` (${f.file})`
    } else if (f.unique_id) {
      where = ` (${f.unique_id})`
    }
    lines.push(`[${f.severity.toUpperCase()}] ${f.code} ${f.alias}${where}`)

3. packages/opencode/src/altimate/tools/dbt-health-config.ts (L34)

[🔴 HIGH] Potential runtime TypeError. Accessing .config directly on the typecasted result.data will throw a TypeError: Cannot read properties of undefined if result.data is null or undefined (even when result.success is true). Please use optional chaining (?.config) to ensure safe access.

Suggested change:

    const yaml = String((result.data as { config?: unknown })?.config ?? "")

4. packages/opencode/src/altimate/tools/dbt-health-config.ts (L35)

[🔴 HIGH] Unsafe path construction and potential directory traversal. The output path is constructed using simple string concatenation. If the AI agent provides an empty string, /, or ../ for project_dir, it could attempt to write to unexpected system locations outside the intended workspace.

Consider importing join from node:path, using join(args.project_dir, DBT_HEALTH_CONFIG_RELPATH), and adding a check to ensure the resolved path remains within the allowed workspace context.

Suggested change:

    // Ensure `join` is imported from `node:path`
    const outPath = join(args.project_dir, DBT_HEALTH_CONFIG_RELPATH)
    // Recommended: add validation to ensure `outPath` is within the workspace root

5. packages/opencode/src/altimate/tools/dbt-health-config.ts (L37-L42)

[🔴 HIGH] Unhandled exceptions in file system operations and inconsistent FS API usage.

  1. Unhandled Exceptions: mkdir and file write operations are not enclosed in a try...catch block. If the tool encounters permission issues or invalid paths, these async calls will throw exceptions that could crash the execution. They should be caught to gracefully return an error payload (similar to !result.success).
  2. Inconsistent API: The code imports mkdir from standard node:fs/promises but uses the runtime-specific Bun.write. For consistency within the file and to avoid potential ReferenceErrors outside of Bun, it's recommended to use writeFile from node:fs/promises.

Suggested change:

    let written = false
    if (args.write !== false) {
      try {
        await mkdir(dirname(outPath), { recursive: true })
        // Ensure `writeFile` is imported from `node:fs/promises` alongside `mkdir`
        await writeFile(outPath, yaml)
        written = true
      } catch (err) {
        return {
          title: "dbt health config: ERROR",
          metadata: { error: String(err) },
          output: `Failed to write config file to ${outPath}: ${err}`,
        }
      }
    }

Comment on lines +38 to +52
let config_json: string | undefined
const candidates = args.config_path
? [args.config_path]
: [
`${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.yml`,
`${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.yaml`,
`${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.json`,
]
for (const candidate of candidates) {
const file = Bun.file(candidate)
if (await file.exists()) {
config_json = await file.text()
break
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[🟠 MEDIUM] If args.config_path is explicitly provided but the file does not exist, this logic will silently proceed with config_json = undefined. It is recommended to throw an error or return an error response when an explicitly provided configuration file is not found, to avoid running health checks with unintended settings.

Suggested change:

Suggested change
let config_json: string | undefined
const candidates = args.config_path
? [args.config_path]
: [
`${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.yml`,
`${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.yaml`,
`${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.json`,
]
for (const candidate of candidates) {
const file = Bun.file(candidate)
if (await file.exists()) {
config_json = await file.text()
break
}
}
let config_json: string | undefined
if (args.config_path) {
const file = Bun.file(args.config_path)
if (!(await file.exists())) {
return {
title: "dbt health: ERROR",
metadata: { error: `Config file not found: ${args.config_path}` },
output: `Failed to find config file at ${args.config_path}`,
}
}
config_json = await file.text()
} else {
const candidates = [
`${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.yml`,
`${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.yaml`,
`${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.json`,
]
for (const candidate of candidates) {
const file = Bun.file(candidate)
if (await file.exists()) {
config_json = await file.text()
break
}
}
}

Comment on lines +93 to +95
for (const f of findings) {
const where = f.file ? ` (${f.file})` : f.unique_id ? ` (${f.unique_id})` : ""
lines.push(`[${f.severity.toUpperCase()}] ${f.code} ${f.alias}${where}`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[🔵 LOW] According to the review checklist, nested ternary expressions are not allowed. Please refactor this to use an if...else statement or logical operators to improve readability.

Suggested change:

Suggested change
for (const f of findings) {
const where = f.file ? ` (${f.file})` : f.unique_id ? ` (${f.unique_id})` : ""
lines.push(`[${f.severity.toUpperCase()}] ${f.code} ${f.alias}${where}`)
for (const f of findings) {
let where = ""
if (f.file) {
where = ` (${f.file})`
} else if (f.unique_id) {
where = ` (${f.unique_id})`
}
lines.push(`[${f.severity.toUpperCase()}] ${f.code} ${f.alias}${where}`)

}
}

const yaml = String((result.data as { config?: unknown }).config ?? "")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[🔴 HIGH] Potential runtime TypeError. Accessing .config directly on the typecasted result.data will throw a TypeError: Cannot read properties of undefined if result.data is null or undefined (even when result.success is true). Please use optional chaining (?.config) to ensure safe access.

Suggested change:

Suggested change
const yaml = String((result.data as { config?: unknown }).config ?? "")
const yaml = String((result.data as { config?: unknown })?.config ?? "")

}

const yaml = String((result.data as { config?: unknown }).config ?? "")
const outPath = `${args.project_dir.replace(/\/+$/, "")}/${DBT_HEALTH_CONFIG_RELPATH}`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[🔴 HIGH] Unsafe path construction and potential directory traversal. The output path is constructed using simple string concatenation. If the AI agent provides an empty string, /, or ../ for project_dir, it could attempt to write to unexpected system locations outside the intended workspace.

Consider importing join from node:path, using join(args.project_dir, DBT_HEALTH_CONFIG_RELPATH), and adding a check to ensure the resolved path remains within the allowed workspace context.

Suggested change:

Suggested change
const outPath = `${args.project_dir.replace(/\/+$/, "")}/${DBT_HEALTH_CONFIG_RELPATH}`
// Ensure `join` is imported from `node:path`
const outPath = join(args.project_dir, DBT_HEALTH_CONFIG_RELPATH)
// Recommended: add validation to ensure `outPath` is within the workspace root

Comment on lines +37 to +42
let written = false
if (args.write !== false) {
await mkdir(dirname(outPath), { recursive: true })
await Bun.write(outPath, yaml)
written = true
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[🔴 HIGH] Unhandled exceptions in file system operations and inconsistent FS API usage.

  1. Unhandled Exceptions: mkdir and file write operations are not enclosed in a try...catch block. If the tool encounters permission issues or invalid paths, these async calls will throw exceptions that could crash the execution. They should be caught to gracefully return an error payload (similar to !result.success).
  2. Inconsistent API: The code imports mkdir from standard node:fs/promises but uses the runtime-specific Bun.write. For consistency within the file and to avoid potential ReferenceErrors outside of Bun, it's recommended to use writeFile from node:fs/promises.

Suggested change:

Suggested change
let written = false
if (args.write !== false) {
await mkdir(dirname(outPath), { recursive: true })
await Bun.write(outPath, yaml)
written = true
}
let written = false
if (args.write !== false) {
try {
await mkdir(dirname(outPath), { recursive: true })
// Ensure `writeFile` is imported from `node:fs/promises` alongside `mkdir`
await writeFile(outPath, yaml)
written = true
} catch (err) {
return {
title: "dbt health config: ERROR",
metadata: { error: String(err) },
output: `Failed to write config file to ${outPath}: ${err}`,
}
}
}

@dev-punia-altimate

Copy link
Copy Markdown
Contributor

🤖 Code Review — OpenCodeReview (Gemini) — No Issues Found

No supported files changed.

@sahrizvi sahrizvi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: 3 critical, 4 major, 10 minor

The design is sound and both tools follow the established Tool.define + Dispatcher.call + {title, metadata, output} shape closely. Three things block merge: a dependency that isn't published yet, a test this PR deterministically breaks, and a file write that sidesteps the repo's permission layer.

Inline comments below cover the critical/major/minor findings. Issues that don't map to a specific line — plus nits, one item needing verification against the core, and what's done well — are in a follow-up comment.

"@ai-sdk/vercel": "2.0.39",
"@ai-sdk/xai": "3.0.82",
"@altimateai/altimate-core": "0.5.1",
"@altimateai/altimate-core": "0.8.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL — 0.8.0 is not published, and bun.lock still pins 0.5.1

npm view @altimateai/altimate-core versions
→ … "0.5.1", "0.6.0", "0.7.0"     dist-tags.latest = 0.7.0

0.8.0 does not exist on npm today. Separately, bun.lock still resolves 0.5.1 at lines 273 and 642 plus all five platform binaries, and the lockfile isn't part of this PR (git log -1 -- bun.lock points at a pre-existing commit).

Every CI job runs plain bun install (.github/workflows/ci.yml:100,108,287,348,408,465,505), and Bun applies frozen-lockfile behaviour when CI is set — so the manifest/lock mismatch fails the install step. If it did install from the current lock it would fetch 0.5.1, which exports neither dbtProjectHealth nor dbtHealthInferConfig, and both tools would return TypeError: core.dbtProjectHealth is not a function on every call.

The PR description already flags the ordering dependency, so this is partly known — noting it because the branch is red until that release lands.

Fix: publish 0.8.0, then bun install and commit the regenerated bun.lock with the root entry and all five platform optional deps at 0.8.0.

One wrinkle: bunfig.toml sets minimumReleaseAge = 259200 (3 days) and this package is not in minimumReleaseAgeExcludes, so a freshly published 0.8.0 will still be refused for three days after publish. Add it to the excludes list or plan the timing.

Comment on lines +270 to +286
// dbt project health checks — reads the dbt project files directly (no manifest required).
register("altimate_core.dbt_project_health", async (params) => {
try {
const json = core.dbtProjectHealth(params.project_dir, params.config_json, params.catalog_path)
return ok(true, { findings: JSON.parse(json) })
} catch (e) {
return fail(e)
}
})
// Deterministically infer a dbt health-check config (YAML) from a project's conventions.
register("altimate_core.dbt_health_infer_config", async (params) => {
try {
return ok(true, { config: core.dbtHealthInferConfig(params.project_dir) })
} catch (e) {
return fail(e)
}
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL — these two registrations break altimate-core-native.test.ts

packages/opencode/test/altimate/altimate-core-native.test.ts:176-231 keeps a hand-maintained ALL_METHODS array (43 entries) and asserts an exact count:

const coreCount = registered.filter((m) => m.startsWith("altimate_core.")).length
expect(coreCount).toBe(ALL_METHODS.length)

Registering two more methods without adding them to the array makes it 45 vs 43. This is a deterministic CI failure, not a coverage gap.

Fix: add "altimate_core.dbt_project_health" and "altimate_core.dbt_health_infer_config" to ALL_METHODS.

// dbt project health checks — reads the dbt project files directly (no manifest required).
register("altimate_core.dbt_project_health", async (params) => {
try {
const json = core.dbtProjectHealth(params.project_dir, params.config_json, params.catalog_path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MINOR — no version guard on the new exports

This file already establishes the guard pattern for exactly this situation at line 176:

params.base_sql && typeof core.lintDiff === "function"

added because lintDiff postdates 0.5.1. The surrounding try/catch does convert a missing-export TypeError into the normal failure envelope, so this is diagnostics quality rather than a crash — but the operator sees a raw TypeError string instead of an actionable message.

Fix: guard with typeof core.dbtProjectHealth === "function" and fail with "dbt_project_health requires @altimateai/altimate-core >= 0.8.0". Same for dbtHealthInferConfig below.

Comment on lines +35 to +42
const outPath = `${args.project_dir.replace(/\/+$/, "")}/${DBT_HEALTH_CONFIG_RELPATH}`

let written = false
if (args.write !== false) {
await mkdir(dirname(outPath), { recursive: true })
await Bun.write(outPath, yaml)
written = true
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL — this write has none of the repo's write guards

project_dir comes straight from the model. There is no path.resolve against Instance.directory, no containment check, no symlink resolution, and no permission prompt. WriteTool (src/tool/write.ts:64-70) resolves against Instance.directory and then gates every write behind assertExternalDirectoryEffect and assertSensitiveWriteEffect.

This is the first write-capable tool under src/altimate/tools/ — no other file there calls Bun.write/writeFile — so it establishes a path around the guard layer.

Three concrete failure modes:

  • an absolute or ../-containing project_dir creates directories and writes files outside the workspace with no prompt;
  • a .altimate directory or .altimate/dbt-health.yml symlink inside an otherwise legitimate project is followed and its external target truncated;
  • mkdir(…, { recursive: true }) creates the whole tree on the way there.

Fix: resolve project_dir against Instance.directory, build the path with path.join, canonicalize and verify containment, then gate the write. This tool uses the Promise/Zod adapter, so call the Promise-style helpers — assertExternalDirectory(ctx, target, options) / assertExternalDirectoryLegacy(...) / assertSensitiveWrite(ctx, target) (src/tool/external-directory.ts:54,89,113) — not the Effect-only variants. Re-validate symlinked path components immediately before writing.

Comment on lines +15 to +18
write: z
.boolean()
.optional()
.describe("Write the config to <project_dir>/.altimate/dbt-health.yml (default true). If false, only return the YAML."),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR — hand-edited config is silently destroyed

write defaults to true and Bun.write at line 40 unconditionally replaces an existing .altimate/dbt-health.yml — no diff, no backup, no overwrite argument, and no mention in the tool description.

.opencode/command/dbt-health-config.md:23 explicitly tells users the file "is committed with the project and can be hand-edited", so the advertised workflow is precisely the one that erases their edits.

Fix: add overwrite: boolean defaulting to false, or open with wx and fail when the file exists. At minimum, report that the file already existed and what changed.

Comment on lines +26 to +29
config_path: z
.string()
.optional()
.describe("Optional path to a JSON health-check config (disabled checks, severity overrides, options)"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MINOR — this description is inaccurate and incomplete

It says "path to a JSON health-check config", but auto-discovery below accepts .yml/.yaml/.json and the sibling dbt_health_config tool writes YAML. As written, the model is steered away from passing the very file it just generated.

The description also never mentions that a project-local config is auto-discovered when this is omitted, so the model can't reason about when to pass it.

Fix: "Optional path to a health-check config (YAML or JSON). When omitted, auto-discovers <project_dir>/.altimate/dbt-health.{yml,yaml,json}."

Comment on lines +42 to +44
`${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.yml`,
`${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.yaml`,
`${args.project_dir.replace(/\/+$/, "")}/.altimate/dbt-health.json`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MINOR — build these with node:path.join

${args.project_dir.replace(/\/+$/, "")}/… hand-rolls path joining in three places here and once more in dbt-health-config.ts:35. It doesn't normalize .. segments and assumes POSIX separators.

Fix: path.join(projectDir, ".altimate", "dbt-health.yml"). dbt-health-config.ts already imports dirname from node:path.

Comment on lines +5 to +17
/** A single finding returned by `altimate_core.dbt_project_health` (Rust `HealthFinding`). */
interface HealthFinding {
code: string
alias: string
resource_type: "model" | "source" | "exposure" | "macro" | "project"
unique_id?: string
file?: string
severity: "error" | "warning" | "info"
message: string
recommendation?: string
reason_to_flag?: string
metadata?: unknown
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MINOR — HealthFinding is an unvalidated local guess

This interface is hand-declared here with no link to the Rust HealthFinding struct and no runtime validation — the shape is only asserted via a cast at line 69. Every other bridge method has a typed result interface in native/types.ts. If the core renames or drops a field, TypeScript won't catch it and the formatter silently emits undefined text.

Fix: move the type into native/types.ts as part of a DbtProjectHealthResult, or parse the JSON through a z.object() schema in the handler.

Comment on lines +15 to +16
reason_to_flag?: string
metadata?: unknown

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MINOR — these two fields are declared but never rendered

formatFindings (lines 90-100) emits code, alias, file/unique_id, message, and recommendationreason_to_flag and metadata never reach the output. reason_to_flag is arguably the single most useful field in a governance report, since it's what tells the reader why a rule fired.

Fix: render reason_to_flag (it's what makes a finding actionable), or drop both from the interface.

Comment on lines +90 to +100
function formatFindings(findings: HealthFinding[]): string {
if (findings.length === 0) return "✓ No dbt health issues found."
const lines: string[] = []
for (const f of findings) {
const where = f.file ? ` (${f.file})` : f.unique_id ? ` (${f.unique_id})` : ""
lines.push(`[${f.severity.toUpperCase()}] ${f.code} ${f.alias}${where}`)
lines.push(` ${f.message}`)
if (f.recommendation) lines.push(` → ${f.recommendation}`)
}
return lines.join("\n")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MINOR — no domain-level cap on output

formatFindings emits 2–3 lines per finding with no limit. This is bounded in practice: the shared Tool.define wrapper applies Truncate.Service at MAX_LINES = 2000 / MAX_BYTES = 50 * 1024 (src/tool/truncate.ts:15-16) and spills the full text to a file, and because findings are sorted error-first the head truncation keeps the important ones while metadata counts stay accurate. That's a good outcome largely by accident of the sort order.

What's missing is that the model is never told it's seeing a partial list.

Fix: cap at a domain-appropriate number and append "… N more findings omitted", so the truncation is explicit rather than inferred.

@sahrizvi

sahrizvi commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Remaining findings — not line-attributable, plus nits

MAJOR — no tests for either tool

Neither dbt_project_health nor dbt_health_config has a test file, in a package with a substantial test/altimate/ suite where every sibling bridge method is covered. Untested paths:

  • config auto-discovery precedence (.yml.yaml.json) and explicit-path-wins
  • the write: false branch, and overwrite behaviour when .altimate/dbt-health.yml already exists
  • severity sorting, the empty-findings path (✓ No dbt health issues found.), unknown severities
  • a non-dbt project_dir, and a dispatcher failure (success: false)
  • both bridge handlers' exact argument order and JSON decoding

Worth adding beyond the basics: absolute and .. project_dir; a symlinked .altimate directory and output file; an explicit config that is missing, unreadable, or malformed; mkdir/write failure; a large finding set exercising truncation; and an install smoke test proving 0.8.0 and all five platform binaries resolve.

Needs verification against the core (couldn't confirm from this repo)

The bridge passes YAML file contents in a parameter named config_json (dbt-project-health.ts:39-52types.ts:1248). The inline comment asserts "YAML or JSON both parse". If the core instead falls back to a default config when it can't parse the input — rather than returning an error — this compounds the config_path issue above: a malformed or wrong-format config produces a clean, empty report rather than a failure.

Two things to confirm on the core side:

  1. Does dbt_project_health actually parse YAML for config_json? If so, rename the parameter to config — the current name will keep misleading callers.
  2. Does an unparseable config error, or silently degrade to defaults? If it degrades, surface the parse error through the envelope.

NITs

  • Non-dbt project_dir produces a Rust-worded error. The native parser does validate the project and the bridge returns it via result.success === false, so this is message quality rather than a crash — but project-scan.ts already has detectDbtProject() if a friendlier pre-check is wanted.
  • Registration ordering. In registry.ts:72-73 the two new imports sit between DbtManifestTool and the altimate_change marker for DbtUnitTestGenTool, without markers of their own; altimate/index.ts:43-44 and the registry list both put dbt-project-health before dbt-health-config, breaking the otherwise-alphabetical grouping.
  • catalog_path isn't reachable from the command flow. dbt_health_config has no catalog_path passthrough, so step 2 of /dbt-health-config can't offer column-aware checks in the same invocation.

Two things worth explicitly retiring

  • result.data.findings needs no null guard — AltimateCoreResult.data is non-optional (types.ts:831-835) and always populated by both ok() and fail() (altimate-core.ts:36-42).
  • The stale "all 34 altimate_core.* bridge methods" module comment at native/altimate-core.ts:2 is pre-existing (already wrong at the base commit), not introduced here — though this PR does make it wronger, if someone wants to fix it in passing.

What's done well

  • Both tools follow the established Tool.define + Dispatcher.call + {title, metadata, output} shape used across ~40 sibling altimate tools. Nothing bespoke, nothing to relearn.
  • The bridge handlers reuse the ok()/fail() envelope correctly and preserve the "success = handler didn't throw; semantics live in data" contract, so a project with findings isn't misreported as a tool failure.
  • Deterministic severity sort with a localeCompare tiebreak gives stable, diffable output — and it's what makes the framework's head-truncation land on the right findings.
  • types.ts entries match the handler param shapes exactly, with config_json / catalog_path correctly modelled as optional.
  • Registration is complete and consistent across dispatcher, tool registry, and barrel export — easy to get partially wired, and this isn't.
  • Config discovery has a sensible deterministic precedence, and /dbt-health-config chains infer-then-run coherently, handles multi-project repos, and explicitly forbids fabricating values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants