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.

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.

2 participants