Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,21 +136,33 @@ runs:
cd /tmp
if curl -fsSL -o "altimate-code-archive" "$DOWNLOAD_URL"; then
if [ "$OS" = "linux" ]; then
tar xzf altimate-code-archive -C ~/.altimate-code/bin/ 2>/dev/null || true
tar xzf altimate-code-archive -C ~/.altimate-code/bin/
else
unzip -o altimate-code-archive -d ~/.altimate-code/bin/ 2>/dev/null || true
unzip -o altimate-code-archive -d ~/.altimate-code/bin/
fi
chmod +x ~/.altimate-code/bin/altimate-code 2>/dev/null || true
chmod +x ~/.altimate-code/bin/altimate-code
rm -f altimate-code-archive
echo "altimate-code installed successfully"

# Verify installation
if [ -x "$HOME/.altimate-code/bin/altimate-code" ]; then
echo "altimate-code installed successfully"
"$HOME/.altimate-code/bin/altimate-code" --version 2>/dev/null || true
else
echo "::warning::altimate-code binary not found after extraction"
ls -la "$HOME/.altimate-code/bin/" || true
fi
else
echo "::warning::Failed to download altimate-code binarysome features may be limited"
echo "::warning::Failed to download altimate-code ${VERSION}falling back to regex rules"
fi

- name: Add altimate-code to PATH
shell: bash
run: |
[ -d "$HOME/.altimate-code/bin" ] && echo "$HOME/.altimate-code/bin" >> $GITHUB_PATH || true
echo "$HOME/.altimate-code/bin" >> $GITHUB_PATH
# Also verify check command is available
if command -v altimate-code >/dev/null 2>&1; then
echo "altimate-code is on PATH"
fi

- name: Setup Node.js
uses: actions/setup-node@v4
Expand Down
22 changes: 17 additions & 5 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27782,11 +27782,11 @@ function detectNonDeterministic(sql, file) {
return findAllMatches(
sql,
file,
/\b(CURRENT_DATE|CURRENT_TIMESTAMP|NOW|GETDATE|SYSDATE|SYSTIMESTAMP)\b(\s*\(\s*\))?/i,
/\b(CURRENT_DATE|CURRENT_TIMESTAMP|NOW|GETDATE|SYSDATE|SYSTIMESTAMP|GENERATE_UUID|UUID|NEWID|RANDOM|RAND|SYS_GUID)\b(\s*\(\s*\))?/i,
"non_deterministic",
"Non-deterministic function detected \u2014 results will vary between runs. Consider parameterizing.",
"warning" /* Warning */,
"Replace with a parameterized date variable or a macro argument, e.g. WHERE created_at > {{ run_date }}"
"Replace with a parameterized value or a macro argument, e.g. pass a variable instead of calling a non-deterministic function"
);
}
function detectCorrelatedSubquery(sql, file) {
Expand Down Expand Up @@ -29066,10 +29066,10 @@ async function main() {
}
async function runAnalyses(sqlFiles, prContext, config) {
const fileConfig = config.fileConfig;
const isV2 = fileConfig && fileConfig.version === 2;
const v2Config = fileConfig && fileConfig.version === 2 ? fileConfig : null;
const promises = [
// SQL review
config.sqlReview && (config.mode === "full" || config.mode === "static" || config.mode === "ai") ? isV2 ? runV2CheckAnalysis(sqlFiles, fileConfig) : analyzeSQLFiles(sqlFiles, config) : Promise.resolve([]),
// SQL review — try CLI check first, fall back to regex
config.sqlReview && (config.mode === "full" || config.mode === "static" || config.mode === "ai") ? runAnalysisWithCLIFallback(sqlFiles, config, v2Config) : Promise.resolve([]),
// Impact analysis
(async () => {
if (!config.impactAnalysis) return null;
Expand Down Expand Up @@ -29118,6 +29118,18 @@ async function runAnalyses(sqlFiles, prContext, config) {
];
return Promise.all(promises);
}
async function runAnalysisWithCLIFallback(sqlFiles, config, v2Config) {
const cliReady = await isCheckCommandAvailable();
if (cliReady) {
core13.info("altimate-code check command available \u2014 using AST-based analysis");
if (v2Config) {
return runV2CheckAnalysis(sqlFiles, v2Config);
}
return runCheckCommand(sqlFiles, { checks: ["lint", "safety"] });
}
core13.info("altimate-code check not available \u2014 using regex rule engine");
return analyzeSQLFiles(sqlFiles, config);
}
async function runV2CheckAnalysis(sqlFiles, v2Config) {
const cliReady = await isCheckCommandAvailable();
if (!cliReady) {
Expand Down
4 changes: 2 additions & 2 deletions dist/index.js.map

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/analysis/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,15 @@ function detectMissingPartition(sql: string, file: string): SQLIssue[] {
}

function detectNonDeterministic(sql: string, file: string): SQLIssue[] {
// Match with or without parens: CURRENT_DATE, CURRENT_DATE(), NOW(), etc.
// Match with or without parens: CURRENT_DATE, NOW(), GENERATE_UUID(), RAND(), etc.
return findAllMatches(
sql,
file,
/\b(CURRENT_DATE|CURRENT_TIMESTAMP|NOW|GETDATE|SYSDATE|SYSTIMESTAMP)\b(\s*\(\s*\))?/i,
/\b(CURRENT_DATE|CURRENT_TIMESTAMP|NOW|GETDATE|SYSDATE|SYSTIMESTAMP|GENERATE_UUID|UUID|NEWID|RANDOM|RAND|SYS_GUID)\b(\s*\(\s*\))?/i,
"non_deterministic",
"Non-deterministic function detected — results will vary between runs. Consider parameterizing.",
Severity.Warning,
"Replace with a parameterized date variable or a macro argument, e.g. WHERE created_at > {{ run_date }}",
"Replace with a parameterized value or a macro argument, e.g. pass a variable instead of calling a non-deterministic function",
);
}

Expand Down
39 changes: 33 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,18 @@ async function runAnalyses(
prContext: Awaited<ReturnType<typeof getPRContext>>,
config: ActionConfig,
): Promise<[SQLIssue[], ImpactResult | null, CostEstimate[]]> {
// Detect v2 config — if present and CLI available, use `altimate-code check`
// Always try `altimate-code check` first (deterministic, no LLM).
// Falls back to regex rules if CLI unavailable.
const fileConfig = (config as ActionConfig & { fileConfig?: AltimateConfig }).fileConfig;
const isV2 = fileConfig && (fileConfig as unknown as { version: number }).version === 2;
const v2Config =
fileConfig && (fileConfig as unknown as { version: number }).version === 2
? (fileConfig as unknown as AltimateConfigV2)
: null;

const promises: [Promise<SQLIssue[]>, Promise<ImpactResult | null>, Promise<CostEstimate[]>] = [
// SQL review
// SQL review — try CLI check first, fall back to regex
config.sqlReview && (config.mode === "full" || config.mode === "static" || config.mode === "ai")
? isV2
? runV2CheckAnalysis(sqlFiles, fileConfig as unknown as AltimateConfigV2)
: analyzeSQLFiles(sqlFiles, config)
? runAnalysisWithCLIFallback(sqlFiles, config, v2Config)
: Promise.resolve([]),

// Impact analysis
Expand Down Expand Up @@ -245,6 +247,31 @@ async function runAnalyses(
* checks are collected and sent as a single CLI invocation. Falls back to
* the standard `analyzeSQLFiles` path if the CLI is not available.
*/
/**
* Try `altimate-code check` first (real AST-based analysis), fall back to
* regex rules if the CLI is unavailable.
*/
async function runAnalysisWithCLIFallback(
sqlFiles: ReturnType<typeof getChangedSQLFiles>,
config: ActionConfig,
v2Config: AltimateConfigV2 | null,
): Promise<SQLIssue[]> {
// Try CLI check
const cliReady = await isCheckCommandAvailable();
if (cliReady) {
core.info("altimate-code check command available — using AST-based analysis");
if (v2Config) {
return runV2CheckAnalysis(sqlFiles, v2Config);
}
// No v2 config — use default checks (lint + safety)
return runCheckCommand(sqlFiles, { checks: ["lint", "safety"] });
}

// CLI not available — fall back to regex
core.info("altimate-code check not available — using regex rule engine");
return analyzeSQLFiles(sqlFiles, config);
}

async function runV2CheckAnalysis(
sqlFiles: ReturnType<typeof getChangedSQLFiles>,
v2Config: AltimateConfigV2,
Expand Down
Loading