Skip to content

Instantly share code, notes, and snippets.

@mohsen1
Last active May 11, 2025 23:33
Show Gist options
  • Select an option

  • Save mohsen1/c867d038fc4f46494af4c4024cfc7939 to your computer and use it in GitHub Desktop.

Select an option

Save mohsen1/c867d038fc4f46494af4c4024cfc7939 to your computer and use it in GitHub Desktop.

Revisions

  1. mohsen1 revised this gist Jan 21, 2025. 1 changed file with 5 additions and 1 deletion.
    6 changes: 5 additions & 1 deletion ask.js
    Original file line number Diff line number Diff line change
    @@ -75,7 +75,11 @@ function debug(message) {
    * @param {string} str
    * @returns {number}
    */
    function reallyDumbTokenCounter(str = "") {
    function reallyDumbTokenCounter(str) {
    if (typeof str !== "string") {
    console.trace("str is not a string", typeof str, str);
    }
    str = typeof str === "string" ? str : "";
    return (
    str
    // Split on whitespace, newlines, and parentheses, brackets, and braces
  2. mohsen1 revised this gist Jan 21, 2025. 2 changed files with 48 additions and 8 deletions.
    2 changes: 2 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -4,6 +4,8 @@ CLI tool that uses DeepSeek AI to debug Rust projects by analyzing test failures

    > This is highly personalized to my own use case. But you can customize it for yourself.
    This will collect all of test failues, filter out passing test, serialize the repo and git changes into one request to ask deepseek for help

    ## Setup

    1. Install [yek](https://github.com/bodo-run/yek)
    54 changes: 46 additions & 8 deletions ask.js
    Original file line number Diff line number Diff line change
    @@ -62,10 +62,36 @@ const systemPrompt = [

    function debug(message) {
    if (debugEnabled) {
    console.log(`[DEBUG] ${message}`);
    console.log(`[ask.js] ${message}`);
    }
    }

    /**
    * Really dumb token counter. It assumes that each word is a token.
    * It's a bit smarter than that though, it splits camelCase,
    * snake_case, PascalCase, and kebab-case multi-word strings into tokens.
    * It also assumes ()[]{} are token separators.
    * Dumb but works for most cases and is fast.
    * @param {string} str
    * @returns {number}
    */
    function reallyDumbTokenCounter(str = "") {
    return (
    str
    // Split on whitespace, newlines, and parentheses, brackets, and braces
    .split(/[\s\n()[\]{}]+/)
    .flatMap((word) =>
    // Split camelCase/PascalCase into separate words
    word
    .split(/(?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z])/)
    // Split snake_case and kebab-case
    .flatMap((part) => part.split(/[_\-]/))
    // Filter out empty strings
    .filter(Boolean)
    ).length
    );
    }

    if (!token) {
    console.error("DEEPSEEK_API_KEY is not set");
    process.exit(1);
    @@ -76,7 +102,7 @@ const maxTokens = 128000;
    // 10000 tokens for test failures
    // Alternatively we can use the word count of trimmedTestOutput but that means running test and serializing
    // can not happen in parallel. 10k characters is good enough for most cases.
    const maxSize = maxTokens - 10000 - systemPrompt.split(/\s/).length;
    const maxSize = maxTokens - 10000 - reallyDumbTokenCounter(systemPrompt);

    // Convert execSync to Promise-based execution
    async function execCommand(program, args = [], options = {}) {
    @@ -178,10 +204,10 @@ const findTestFiles = async (tests) => {
    return Array.from(results);
    };

    // Truncate and escape content if too large
    // Truncate and escape content if too large (from bottom up)
    const truncateAndEscape = (str) => {
    if (str.length > maxTokens) {
    str = str.slice(0, maxTokens) + "... (truncated)";
    if (reallyDumbTokenCounter(str) > maxTokens) {
    str = "... (truncated) ...\n" + str.slice(-maxTokens);
    }
    return JSON.stringify(str);
    };
    @@ -194,8 +220,9 @@ Promise.all([
    returnError: true,
    printToConsole: true,
    }),
    execCommand("git", ["diff", "|", "cat"]),
    ])
    .then(async ([serialized, testOutput]) => {
    .then(async ([serialized, testOutput, gitDiff]) => {
    debug("Serializing and test run complete");

    // Check if any test failed by looking for "test result: FAILED" in the output
    @@ -244,7 +271,9 @@ Promise.all([
    process.exit(1);
    }

    console.log("Asking DeepSeek R1. This will take a while...");
    const timer = setInterval(() => {
    process.stdout.write(".");
    }, 1000);

    // Any lines before "failures:" is not needed. Those are tests that passed.
    const trimmedTestOutput = testOutput.split("failures:").slice(1).join("\n");
    @@ -253,13 +282,20 @@ Promise.all([
    [
    `# Repo:`,
    serialized,
    `# Git diff:`,
    gitDiff,
    `# Test contents:`,
    testContents.join("\n\n"),
    `# Test failures:`,
    trimmedTestOutput,
    ].join("\n\n")
    );
    debug(`Content length: ${content.length} characters`);
    debug(`Content length: ${reallyDumbTokenCounter(content)} tokens`);
    console.log(
    `Asking DeepSeek R1 a ${reallyDumbTokenCounter(
    content
    )} token question. This will take a while...`
    );

    const data = JSON.stringify({
    model: "deepseek-reasoner",
    @@ -294,6 +330,7 @@ Promise.all([
    });

    res.on("end", () => {
    clearInterval(timer);
    debug("Response completed");
    try {
    const jsonResponse = JSON.parse(responseData);
    @@ -314,6 +351,7 @@ Promise.all([
    });

    req.on("error", (error) => {
    clearInterval(timer);
    console.error("Error:", error);
    debug(`Request error: ${error.message}`);
    process.exit(1);
  3. mohsen1 revised this gist Jan 21, 2025. 2 changed files with 57 additions and 50 deletions.
    2 changes: 2 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,8 @@

    CLI tool that uses DeepSeek AI to debug Rust projects by analyzing test failures.

    > This is highly personalized to my own use case. But you can customize it for yourself.
    ## Setup

    1. Install [yek](https://github.com/bodo-run/yek)
    105 changes: 55 additions & 50 deletions ask.js
    Original file line number Diff line number Diff line change
    @@ -73,7 +73,10 @@ if (!token) {

    const maxTokens = 128000;
    // DeepSeek maximum context length is 128K tokens. we leave some room for the test failures.
    const maxSize = maxTokens - 10000 - systemPrompt.split(/\s/).length; // 10000 tokens for test failures
    // 10000 tokens for test failures
    // Alternatively we can use the word count of trimmedTestOutput but that means running test and serializing
    // can not happen in parallel. 10k characters is good enough for most cases.
    const maxSize = maxTokens - 10000 - systemPrompt.split(/\s/).length;

    // Convert execSync to Promise-based execution
    async function execCommand(program, args = [], options = {}) {
    @@ -134,6 +137,54 @@ async function execCommand(program, args = [], options = {}) {
    }
    });
    }
    const findTestFiles = async (tests) => {
    const results = new Set();

    for (const test of tests) {
    try {
    // Search in tests directory first
    const testsResult = execSync(
    `find ./tests -type f -name "*.rs" -exec grep -l "${test}" {} \\;`,
    {
    stdio: ["pipe", "pipe", "pipe"],
    }
    )
    .toString()
    .trim();

    if (testsResult) {
    testsResult.split("\n").forEach((file) => results.add(file));
    continue;
    }

    // If not found in tests, search in src
    const srcResult = execSync(
    `find ./src -type f -name "*.rs" -exec grep -l "${test}" {} \\;`,
    {
    stdio: ["pipe", "pipe", "pipe"],
    }
    )
    .toString()
    .trim();

    if (srcResult) {
    srcResult.split("\n").forEach((file) => results.add(file));
    }
    } catch (error) {
    debug(`Error finding test file for ${test}: ${error.message}`);
    }
    }

    return Array.from(results);
    };

    // Truncate and escape content if too large
    const truncateAndEscape = (str) => {
    if (str.length > maxTokens) {
    str = str.slice(0, maxTokens) + "... (truncated)";
    }
    return JSON.stringify(str);
    };

    // Run serialization and testing in parallel
    debug("Starting serialization and testing in parallel...");
    @@ -168,47 +219,6 @@ Promise.all([

    debug(`Failed tests: ${failedTests.join(", ")}`);

    const findTestFiles = async (tests) => {
    const results = new Set();

    for (const test of tests) {
    try {
    // Search in tests directory first
    const testsResult = execSync(
    `find ./tests -type f -name "*.rs" -exec grep -l "${test}" {} \\;`,
    {
    stdio: ["pipe", "pipe", "pipe"],
    }
    )
    .toString()
    .trim();

    if (testsResult) {
    testsResult.split("\n").forEach((file) => results.add(file));
    continue;
    }

    // If not found in tests, search in src
    const srcResult = execSync(
    `find ./src -type f -name "*.rs" -exec grep -l "${test}" {} \\;`,
    {
    stdio: ["pipe", "pipe", "pipe"],
    }
    )
    .toString()
    .trim();

    if (srcResult) {
    srcResult.split("\n").forEach((file) => results.add(file));
    }
    } catch (error) {
    debug(`Error finding test file for ${test}: ${error.message}`);
    }
    }

    return Array.from(results);
    };

    const testFiles = await findTestFiles(failedTests);

    if (testFiles.length === 0) {
    @@ -236,13 +246,8 @@ Promise.all([

    console.log("Asking DeepSeek R1. This will take a while...");

    // Truncate and escape content if too large
    const truncateAndEscape = (str) => {
    if (str.length > maxTokens) {
    str = str.slice(0, maxTokens) + "... (truncated)";
    }
    return JSON.stringify(str);
    };
    // Any lines before "failures:" is not needed. Those are tests that passed.
    const trimmedTestOutput = testOutput.split("failures:").slice(1).join("\n");

    const content = truncateAndEscape(
    [
    @@ -251,7 +256,7 @@ Promise.all([
    `# Test contents:`,
    testContents.join("\n\n"),
    `# Test failures:`,
    testOutput,
    trimmedTestOutput,
    ].join("\n\n")
    );
    debug(`Content length: ${content.length} characters`);
  4. mohsen1 revised this gist Jan 21, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@ CLI tool that uses DeepSeek AI to debug Rust projects by analyzing test failures

    ## Setup

    1. Install [yek](https://github.com/bodo-run/yek): `npm install -g yek`
    1. Install [yek](https://github.com/bodo-run/yek)
    2. Set API key: `export DEEPSEEK_API_KEY=your_key`

    ## Usage
  5. mohsen1 renamed this gist Jan 21, 2025. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  6. mohsen1 revised this gist Jan 21, 2025. 1 changed file with 26 additions and 0 deletions.
    26 changes: 26 additions & 0 deletions README.me
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,26 @@
    # ask.js

    CLI tool that uses DeepSeek AI to debug Rust projects by analyzing test failures.

    ## Setup

    1. Install [yek](https://github.com/bodo-run/yek): `npm install -g yek`
    2. Set API key: `export DEEPSEEK_API_KEY=your_key`

    ## Usage

    Run in your Rust project: `./ask.js`

    Options:
    - `--debug`: Enable debug logging

    The script runs tests, analyzes failures, and returns AI-powered debugging recommendations.

    ## Cursor AI Editor

    I have this in Cursor AI Editor settings:

    ```
    IMPORTANT: to run the tests execute `./ask.js`. this will take up to 5 minutes. wait long enough
    IMPORTANT: DO NOT KILL ask.js too quickly while asking DeepSeek
    ```
  7. mohsen1 revised this gist Jan 21, 2025. 1 changed file with 39 additions and 12 deletions.
    51 changes: 39 additions & 12 deletions ask.js
    Original file line number Diff line number Diff line change
    @@ -14,7 +14,6 @@
    const { spawn, execSync } = require("child_process");
    const https = require("https");
    const fs = require("fs");
    const path = require("path");

    const token = process.env.DEEPSEEK_API_KEY;

    @@ -23,6 +22,44 @@ const testCommand = "cargo test"; // TODO: make this configurable
    const testProgram = testCommand.split(" ")[0];
    const testArgs = testCommand.split(" ").slice(1);

    const systemPrompt = [
    "You are a senior Rust engineer with 10+ years of experience in systems programming.",
    "Your expertise includes:",
    "- Deep knowledge of Rust's ownership system, lifetimes, and concurrency model",
    "- Mastery of cargo, clippy, and modern Rust toolchain features",
    "- Experience debugging complex memory issues and performance bottlenecks",
    "- Familiarity with common Rust crates and idiomatic patterns",

    "When analyzing test failures:",
    "1. First clearly identify the failure type (compiler error, runtime panic, logical error, performance issue)",
    "2. Analyze backtraces and error messages with attention to ownership boundaries",
    "3. Consider common Rust pitfalls:",
    " - Lifetime mismatches",
    " - Unsafe code violations",
    " - Trait bound errors",
    " - Concurrency race conditions",
    " - Iterator invalidation",
    "4. Cross-reference with cargo test output and clippy warnings",

    "For proposed fixes:",
    "- Always prioritize type safety and borrow checker rules",
    "- Prefer idiomatic solutions over clever hacks",
    "- Include exact code diffs using markdown format with file names",
    "- Explain the root cause before presenting fixes",
    "- Suggest relevant clippy lints or cargo checks to prevent regressions",

    "Response guidelines:",
    "- Structure analysis using bullet points for clarity",
    "- Use code fences for error snippets and diffs",
    "- Highlight connections between test failures and system architecture",
    "- When uncertain, propose multiple hypothesis with verification strategies",

    "Special capabilities:",
    "- Leverage knowledge of Rust internals (MIR, drop order, etc.)",
    "- Reference similar issues in popular Rust OSS projects",
    "- Suggest property-based testing strategies for edge cases",
    ].join("\n");

    function debug(message) {
    if (debugEnabled) {
    console.log(`[DEBUG] ${message}`);
    @@ -34,10 +71,9 @@ if (!token) {
    process.exit(1);
    }

    debug("Serializing...");
    const maxTokens = 128000;
    // DeepSeek maximum context length is 128K tokens. we leave some room for the test failures.
    const maxSize = maxTokens - 10000; // 10000 tokens for test failures
    const maxSize = maxTokens - 10000 - systemPrompt.split(/\s/).length; // 10000 tokens for test failures

    // Convert execSync to Promise-based execution
    async function execCommand(program, args = [], options = {}) {
    @@ -220,15 +256,6 @@ Promise.all([
    );
    debug(`Content length: ${content.length} characters`);

    const systemPrompt = `You are a an expert Rust developer. You are familiar with the Rust language and its ecosystem.
    You use modern Rust and the latest Rust features.
    You are given a Rust project and some test failures.
    Your task is to help the user debug the test failures.
    You should provide a detailed explanation of the test failures and how to fix them.
    Keep your response concise and to the point.
    Write **high-quality** and **clean** code.
    `;

    const data = JSON.stringify({
    model: "deepseek-reasoner",
    messages: [
  8. mohsen1 revised this gist Jan 21, 2025. 1 changed file with 5 additions and 4 deletions.
    9 changes: 5 additions & 4 deletions ask.js
    Original file line number Diff line number Diff line change
    @@ -1,13 +1,14 @@
    #!/usr/bin/env node
    // @ts-check

    /** @see https://github.com/bodo-run/yek */

    /**
    * @fileoverview
    * This script asks DeepSeek to help with debugging a Rust project.
    * It serializes the project, gets test failures, and sends the content to DeepSeek.
    * The response is then printed to the console.
    *
    * YOU WILL NEED `yek` to be installed
    * @see https://github.com/bodo-run/yek
    */

    const { spawn, execSync } = require("child_process");
    @@ -197,7 +198,7 @@ Promise.all([
    process.exit(1);
    }

    debug("Asking deepseek...");
    console.log("Asking DeepSeek R1. This will take a while...");

    // Truncate and escape content if too large
    const truncateAndEscape = (str) => {
    @@ -229,7 +230,7 @@ Promise.all([
    `;

    const data = JSON.stringify({
    model: "deepseek-chat",
    model: "deepseek-reasoner",
    messages: [
    { role: "system", content: systemPrompt },
    { role: "user", content },
  9. mohsen1 revised this gist Jan 21, 2025. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions ask.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,8 @@
    #!/usr/bin/env node
    // @ts-check

    /** @see https://github.com/bodo-run/yek */

    /**
    * @fileoverview
    * This script asks DeepSeek to help with debugging a Rust project.
  10. mohsen1 revised this gist Jan 21, 2025. 1 changed file with 17 additions and 17 deletions.
    34 changes: 17 additions & 17 deletions ask.js
    Original file line number Diff line number Diff line change
    @@ -51,15 +51,15 @@ async function execCommand(program, args = [], options = {}) {
    process.stdout.on("data", (data) => {
    const str = data.toString();
    outputs.push(str);
    if (options.stdio === "inherit") {
    if (options.printToConsole) {
    console.log(str);
    }
    });

    process.stderr.on("data", (data) => {
    const str = data.toString();
    outputs.push(str);
    if (options.stdio === "inherit") {
    if (options.printToConsole) {
    console.error(str);
    }
    });
    @@ -99,31 +99,31 @@ async function execCommand(program, args = [], options = {}) {
    // Run serialization and testing in parallel
    debug("Starting serialization and testing in parallel...");
    Promise.all([
    execCommand("yek", [`--max-size`, maxSize.toString(), `--tokens`], {
    stdio: ["pipe", "pipe", "pipe"],
    }),
    execCommand("yek", [`--max-size`, maxSize.toString(), `--tokens`], {}),
    execCommand(testProgram, testArgs, {
    returnError: true,
    stdio: "inherit",
    printToConsole: true,
    }),
    ])
    .then(async ([serialized, testOutput]) => {
    debug("Serializing and test run complete");

    // Check if any test failed by looking for "test result: FAILED" in the output
    const hasFailures = testOutput.includes("test result: FAILED");
    if (!hasFailures) {
    console.log("All tests passed!");
    process.exit(0);
    }

    // Extract failed test names
    const failedTests = testOutput
    .split("\n")
    .map((line) => line.trim())
    .filter(
    (line) =>
    line.toLowerCase().includes("test") &&
    line.toLowerCase().endsWith("failed")
    )
    .map((line) => {
    const parts = line.split(" ");
    return parts.find((part) => part.includes("::")) || parts[1];
    })
    .filter(Boolean);
    .filter((line) => line.toLowerCase().endsWith("failed"))
    .map((line) => line.split(" ")?.[1]);

    if (failedTests.length === 0) {
    console.error("No test failures found in the output");
    console.log("All tests passed!");
    process.exit(0);
    }

  11. mohsen1 revised this gist Jan 21, 2025. 1 changed file with 99 additions and 8 deletions.
    107 changes: 99 additions & 8 deletions ask.js
    Original file line number Diff line number Diff line change
    @@ -8,13 +8,15 @@
    * The response is then printed to the console.
    */

    const { spawn } = require("child_process");
    const { spawn, execSync } = require("child_process");
    const https = require("https");
    const fs = require("fs");
    const path = require("path");

    const token = process.env.DEEPSEEK_API_KEY;

    const debugEnabled = process.argv.includes("--debug");
    const testCommand = process.argv[2] || "cargo test";
    const testCommand = "cargo test"; // TODO: make this configurable
    const testProgram = testCommand.split(" ")[0];
    const testArgs = testCommand.split(" ").slice(1);

    @@ -102,14 +104,96 @@ Promise.all([
    }),
    execCommand(testProgram, testArgs, {
    returnError: true,
    stdio: "inherit",
    }),
    ])
    .then(([serialized, testOutput]) => {
    const testFailures = testOutput
    .then(async ([serialized, testOutput]) => {
    const failedTests = testOutput
    .split("\n")
    .filter((line) => line.match(/test .* failed/))
    .join("\n")
    .trim();
    .map((line) => line.trim())
    .filter(
    (line) =>
    line.toLowerCase().includes("test") &&
    line.toLowerCase().endsWith("failed")
    )
    .map((line) => {
    const parts = line.split(" ");
    return parts.find((part) => part.includes("::")) || parts[1];
    })
    .filter(Boolean);

    if (failedTests.length === 0) {
    console.error("No test failures found in the output");
    process.exit(0);
    }

    debug(`Failed tests: ${failedTests.join(", ")}`);

    const findTestFiles = async (tests) => {
    const results = new Set();

    for (const test of tests) {
    try {
    // Search in tests directory first
    const testsResult = execSync(
    `find ./tests -type f -name "*.rs" -exec grep -l "${test}" {} \\;`,
    {
    stdio: ["pipe", "pipe", "pipe"],
    }
    )
    .toString()
    .trim();

    if (testsResult) {
    testsResult.split("\n").forEach((file) => results.add(file));
    continue;
    }

    // If not found in tests, search in src
    const srcResult = execSync(
    `find ./src -type f -name "*.rs" -exec grep -l "${test}" {} \\;`,
    {
    stdio: ["pipe", "pipe", "pipe"],
    }
    )
    .toString()
    .trim();

    if (srcResult) {
    srcResult.split("\n").forEach((file) => results.add(file));
    }
    } catch (error) {
    debug(`Error finding test file for ${test}: ${error.message}`);
    }
    }

    return Array.from(results);
    };

    const testFiles = await findTestFiles(failedTests);

    if (testFiles.length === 0) {
    console.error("Could not find any test files");
    process.exit(1);
    }

    debug(`Test files: ${testFiles.join(", ")}`);

    const testContents = testFiles
    .map((filename) => {
    try {
    return fs.readFileSync(filename, "utf8");
    } catch (error) {
    debug(`Error reading file ${filename}: ${error.message}`);
    return "";
    }
    })
    .filter(Boolean);

    if (testContents.length === 0) {
    console.error("Could not read any test files");
    process.exit(1);
    }

    debug("Asking deepseek...");

    @@ -122,7 +206,14 @@ Promise.all([
    };

    const content = truncateAndEscape(
    `# Repo:\n\n${serialized}\n\n# Test failures:\n\n${testFailures}`
    [
    `# Repo:`,
    serialized,
    `# Test contents:`,
    testContents.join("\n\n"),
    `# Test failures:`,
    testOutput,
    ].join("\n\n")
    );
    debug(`Content length: ${content.length} characters`);

  12. mohsen1 revised this gist Jan 21, 2025. 1 changed file with 15 additions and 11 deletions.
    26 changes: 15 additions & 11 deletions ask.js
    Original file line number Diff line number Diff line change
    @@ -42,21 +42,25 @@ async function execCommand(program, args = [], options = {}) {
    debug(`Running: ${program} ${args.join(" ")}`);
    const process = spawn(program, args, {
    shell: true,
    stdio: options.stdio || "inherit",
    stdio: ["pipe", "pipe", "pipe"], // Always pipe to capture output
    ...options,
    });

    if (process.stdout) {
    process.stdout.on("data", (data) => {
    outputs.push(data);
    });
    }
    process.stdout.on("data", (data) => {
    const str = data.toString();
    outputs.push(str);
    if (options.stdio === "inherit") {
    console.log(str);
    }
    });

    if (process.stderr) {
    process.stderr.on("data", (data) => {
    outputs.push(data);
    });
    }
    process.stderr.on("data", (data) => {
    const str = data.toString();
    outputs.push(str);
    if (options.stdio === "inherit") {
    console.error(str);
    }
    });

    process.on("error", (error) => {
    if (options.returnError) {
  13. mohsen1 revised this gist Jan 21, 2025. 1 changed file with 144 additions and 81 deletions.
    225 changes: 144 additions & 81 deletions ask.js
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,5 @@
    #!/usr/bin/env node
    // @ts-check

    /**
    * @fileoverview
    @@ -7,71 +8,121 @@
    * The response is then printed to the console.
    */

    const { execSync } = require('child_process');
    const https = require('https');
    const { spawn } = require("child_process");
    const https = require("https");

    const token = process.env.DEEPSEEK_API_KEY;
    const testCommand = process.argv[2] || 'cargo test';

    const debugEnabled = process.argv.includes('--debug');
    const debugEnabled = process.argv.includes("--debug");
    const testCommand = process.argv[2] || "cargo test";
    const testProgram = testCommand.split(" ")[0];
    const testArgs = testCommand.split(" ").slice(1);

    function debug(message) {
    if (debugEnabled) {
    console.log(`[DEBUG] ${message}`);
    }
    if (debugEnabled) {
    console.log(`[DEBUG] ${message}`);
    }
    }

    if (!token) {
    console.error('DEEPSEEK_API_KEY is not set');
    process.exit(1);
    console.error("DEEPSEEK_API_KEY is not set");
    process.exit(1);
    }

    debug('Serializing...');
    debug("Serializing...");
    const maxTokens = 128000;
    // DeepSeek maximum context length is 128K tokens. we leave some room for the test failures.
    const maxSize = 128000 - 10000; // 10000 tokens for test failures
    const maxSize = maxTokens - 10000; // 10000 tokens for test failures

    // Convert execSync to Promise-based execution
    function execCommand(command, options = {}) {
    return new Promise((resolve, reject) => {
    try {
    const result = execSync(command, { ...options, encoding: 'utf8' });
    resolve(result);
    } catch (error) {
    if (options.returnError) {
    resolve(error.stdout + error.stderr);
    } else {
    reject(error);
    }
    async function execCommand(program, args = [], options = {}) {
    const outputs = [];
    return new Promise((resolve, reject) => {
    try {
    debug(`Running: ${program} ${args.join(" ")}`);
    const process = spawn(program, args, {
    shell: true,
    stdio: options.stdio || "inherit",
    ...options,
    });

    if (process.stdout) {
    process.stdout.on("data", (data) => {
    outputs.push(data);
    });
    }

    if (process.stderr) {
    process.stderr.on("data", (data) => {
    outputs.push(data);
    });
    }

    process.on("error", (error) => {
    if (options.returnError) {
    resolve(outputs.join(""));
    } else {
    reject(error);
    }
    });
    });

    process.on("close", (code) => {
    const output = outputs.join("");
    if (code !== 0) {
    if (options.returnError) {
    resolve(output);
    } else {
    reject(
    new Error(`Command failed with code ${code}\nOutput: ${output}`)
    );
    }
    } else {
    resolve(output);
    }
    });
    } catch (error) {
    if (options.returnError) {
    resolve(outputs.join(""));
    } else {
    reject(error);
    }
    }
    });
    }

    // Run serialization and testing in parallel
    debug('Starting serialization and testing in parallel...');
    debug("Starting serialization and testing in parallel...");
    Promise.all([
    execCommand(`yek --max-size ${maxSize} --tokens`),
    execCommand(testCommand, { stdio: ['pipe', 'pipe', 'pipe'], returnError: true })
    execCommand("yek", [`--max-size`, maxSize.toString(), `--tokens`], {
    stdio: ["pipe", "pipe", "pipe"],
    }),
    execCommand(testProgram, testArgs, {
    returnError: true,
    }),
    ])
    .then(([serialized, testOutput]) => {
    const testFailures = testOutput.split('\n')
    .filter(line => line.match(/test .* failed/))
    .join('\n')
    .trim();

    debug('Asking deepseek...');

    .then(([serialized, testOutput]) => {
    const testFailures = testOutput
    .split("\n")
    .filter((line) => line.match(/test .* failed/))
    .join("\n")
    .trim();

    debug("Asking deepseek...");

    // Truncate and escape content if too large
    const maxContentLength = 30000; // Adjust this value as needed
    const truncateAndEscape = (str) => {
    if (str.length > maxContentLength) {
    str = str.slice(0, maxContentLength) + '... (truncated)';
    }
    return JSON.stringify(str);
    if (str.length > maxTokens) {
    str = str.slice(0, maxTokens) + "... (truncated)";
    }
    return JSON.stringify(str);
    };

    const content = truncateAndEscape(`Repo:\n\n${serialized}\n\nTest failures:\n\n${testFailures}`);
    const systemPrompt =
    `You are a an expert Rust developer. You are familiar with the Rust language and its ecosystem.
    const content = truncateAndEscape(
    `# Repo:\n\n${serialized}\n\n# Test failures:\n\n${testFailures}`
    );
    debug(`Content length: ${content.length} characters`);

    const systemPrompt = `You are a an expert Rust developer. You are familiar with the Rust language and its ecosystem.
    You use modern Rust and the latest Rust features.
    You are given a Rust project and some test failures.
    Your task is to help the user debug the test failures.
    @@ -81,57 +132,69 @@ Promise.all([
    `;

    const data = JSON.stringify({
    model: 'deepseek-chat',
    messages: [
    { role: 'system', content: systemPrompt },
    { role: 'user', content }
    ],
    stream: false
    model: "deepseek-chat",
    messages: [
    { role: "system", content: systemPrompt },
    { role: "user", content },
    ],
    stream: false,
    });

    debug(`Request payload size: ${Buffer.byteLength(data)} bytes`);

    const options = {
    hostname: 'api.deepseek.com',
    path: '/chat/completions',
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`,
    'Content-Length': Buffer.byteLength(data)
    }
    hostname: "api.deepseek.com",
    path: "/chat/completions",
    method: "POST",
    headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${token}`,
    "Content-Length": Buffer.byteLength(data),
    },
    };

    debug("Sending request to DeepSeek API...");
    const req = https.request(options, (res) => {
    let responseData = '';
    debug(`Response status: ${res.statusCode} ${res.statusMessage}`);
    let responseData = "";

    res.on('data', (chunk) => {
    responseData += chunk;
    });
    res.on("data", (chunk) => {
    responseData += chunk;
    debug(`Received chunk of ${chunk.length} bytes`);
    });

    res.on('end', () => {
    try {
    const jsonResponse = JSON.parse(responseData);
    const content = jsonResponse?.choices?.[0]?.message?.content;
    if (content) {
    console.log(content);
    } else {
    console.error('No content found in the response');
    }
    } catch (error) {
    console.error('Failed to parse response:', responseData);
    process.exit(1);
    }
    });
    res.on("end", () => {
    debug("Response completed");
    try {
    const jsonResponse = JSON.parse(responseData);
    debug(`Parsed response successfully`);
    const content = jsonResponse?.choices?.[0]?.message?.content;
    if (content) {
    console.log(content);
    } else {
    console.error("No content found in the response");
    debug(`Full response: ${JSON.stringify(jsonResponse, null, 2)}`);
    }
    } catch (error) {
    console.error("Failed to parse response:", responseData);
    debug(`Parse error: ${error.message}`);
    process.exit(1);
    }
    });
    });

    req.on('error', (error) => {
    console.error('Error:', error);
    process.exit(1);
    req.on("error", (error) => {
    console.error("Error:", error);
    debug(`Request error: ${error.message}`);
    process.exit(1);
    });

    debug("Writing request payload...");
    req.write(data);
    debug("Ending request");
    req.end();
    })
    .catch(error => {
    console.error('Error:', error);
    })
    .catch((error) => {
    console.error("Error:", error);
    process.exit(1);
    });
    });
  14. mohsen1 created this gist Jan 20, 2025.
    137 changes: 137 additions & 0 deletions ask.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,137 @@
    #!/usr/bin/env node

    /**
    * @fileoverview
    * This script asks DeepSeek to help with debugging a Rust project.
    * It serializes the project, gets test failures, and sends the content to DeepSeek.
    * The response is then printed to the console.
    */

    const { execSync } = require('child_process');
    const https = require('https');

    const token = process.env.DEEPSEEK_API_KEY;
    const testCommand = process.argv[2] || 'cargo test';

    const debugEnabled = process.argv.includes('--debug');

    function debug(message) {
    if (debugEnabled) {
    console.log(`[DEBUG] ${message}`);
    }
    }

    if (!token) {
    console.error('DEEPSEEK_API_KEY is not set');
    process.exit(1);
    }

    debug('Serializing...');
    // DeepSeek maximum context length is 128K tokens. we leave some room for the test failures.
    const maxSize = 128000 - 10000; // 10000 tokens for test failures

    // Convert execSync to Promise-based execution
    function execCommand(command, options = {}) {
    return new Promise((resolve, reject) => {
    try {
    const result = execSync(command, { ...options, encoding: 'utf8' });
    resolve(result);
    } catch (error) {
    if (options.returnError) {
    resolve(error.stdout + error.stderr);
    } else {
    reject(error);
    }
    }
    });
    }

    // Run serialization and testing in parallel
    debug('Starting serialization and testing in parallel...');
    Promise.all([
    execCommand(`yek --max-size ${maxSize} --tokens`),
    execCommand(testCommand, { stdio: ['pipe', 'pipe', 'pipe'], returnError: true })
    ])
    .then(([serialized, testOutput]) => {
    const testFailures = testOutput.split('\n')
    .filter(line => line.match(/test .* failed/))
    .join('\n')
    .trim();

    debug('Asking deepseek...');

    // Truncate and escape content if too large
    const maxContentLength = 30000; // Adjust this value as needed
    const truncateAndEscape = (str) => {
    if (str.length > maxContentLength) {
    str = str.slice(0, maxContentLength) + '... (truncated)';
    }
    return JSON.stringify(str);
    };

    const content = truncateAndEscape(`Repo:\n\n${serialized}\n\nTest failures:\n\n${testFailures}`);
    const systemPrompt =
    `You are a an expert Rust developer. You are familiar with the Rust language and its ecosystem.
    You use modern Rust and the latest Rust features.
    You are given a Rust project and some test failures.
    Your task is to help the user debug the test failures.
    You should provide a detailed explanation of the test failures and how to fix them.
    Keep your response concise and to the point.
    Write **high-quality** and **clean** code.
    `;

    const data = JSON.stringify({
    model: 'deepseek-chat',
    messages: [
    { role: 'system', content: systemPrompt },
    { role: 'user', content }
    ],
    stream: false
    });

    const options = {
    hostname: 'api.deepseek.com',
    path: '/chat/completions',
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`,
    'Content-Length': Buffer.byteLength(data)
    }
    };

    const req = https.request(options, (res) => {
    let responseData = '';

    res.on('data', (chunk) => {
    responseData += chunk;
    });

    res.on('end', () => {
    try {
    const jsonResponse = JSON.parse(responseData);
    const content = jsonResponse?.choices?.[0]?.message?.content;
    if (content) {
    console.log(content);
    } else {
    console.error('No content found in the response');
    }
    } catch (error) {
    console.error('Failed to parse response:', responseData);
    process.exit(1);
    }
    });
    });

    req.on('error', (error) => {
    console.error('Error:', error);
    process.exit(1);
    });

    req.write(data);
    req.end();
    })
    .catch(error => {
    console.error('Error:', error);
    process.exit(1);
    });