Source: pipeline/promptHelpers.js

/**
 * promptHelpers.js — Shared prompt utilities for test generation
 *
 * Pure functions used by all prompt builders:
 *   - resolveTestCountInstruction(testCount, local) → imperative AI instruction
 *   - withDials(base, dialsPrompt)                  → inject dials into prompt
 *
 * Supports both legacy string prompts and structured { system, user } messages.
 */

import { isLocalProvider } from "../aiProvider.js";
import { shouldLog, formatLogLine } from "../utils/logFormatter.js";

/**
 * Resolve the test count instruction for prompt builders.
 *
 * Maps the validated testCount dial value to an authoritative instruction
 * string that replaces the previously hardcoded "Generate 3-5 / 5-8 tests"
 * ranges.  The instruction is worded imperatively so the LLM treats it as a
 * hard constraint rather than a suggestion.
 *
 * @param {string} testCount — validated dial value (one|small|medium|large|ai_decides)
 * @param {boolean} [local]  — true when using a local provider (Ollama).
 *                              Defaults to isLocalProvider() when omitted.
 * @returns {string} e.g. "Generate EXACTLY 1 test" or "Generate 5-8 tests"
 */
export function resolveTestCountInstruction(testCount, local) {
  if (local === undefined) local = isLocalProvider();
  switch (testCount) {
    case "one":       return "Generate EXACTLY 1 test";
    case "small":     return "Generate EXACTLY 3-5 tests";
    case "medium":    return "Generate EXACTLY 6-10 tests";
    case "large":     return "Generate EXACTLY 10-20 tests";
    case "ai_decides":
    default:          return `Generate ${local ? "3-5" : "5-8"} tests`;
  }
}

// ── Internal: inject dials into a plain string prompt ────────────────────────

function injectDialsIntoString(base, dialsPrompt) {
  // Find the best injection point — before the rules section
  const markers = ["STRICT RULES:", "Requirements:"];
  for (const marker of markers) {
    const idx = base.indexOf(marker);
    if (idx !== -1) {
      return (
        base.slice(0, idx).trimEnd() +
        "\n\n" + dialsPrompt + "\n\n" +
        base.slice(idx)
      );
    }
  }
  // Fallback: append at end
  return `${base}\n\n${dialsPrompt}`;
}

/**
 * Inject an optional dialsPrompt into a base AI prompt.
 *
 * Accepts either:
 *   - A plain string (legacy) → injects before STRICT RULES / Requirements
 *   - A { system, user } object → appends dials to the end of the user message
 *
 * Returns the same shape as the input (string or { system, user }).
 *
 * Dials are injected into the USER message (not system) because they represent
 * per-request configuration that varies with each generation run.
 */
export function withDials(base, dialsPrompt) {
  if (!dialsPrompt) {
    if (shouldLog("debug")) {
      const len = typeof base === "string" ? base.length : (base.system?.length || 0) + (base.user?.length || 0);
      console.log(formatLogLine("debug", null, `[withDials] No dials prompt — using base prompt (${len} chars)`));
    }
    return base;
  }

  // ── Structured { system, user } messages ──────────────────────────────────
  if (typeof base === "object" && base.user) {
    const user = injectDialsIntoString(base.user, dialsPrompt);
    if (shouldLog("debug")) {
      console.log(formatLogLine("debug", null, `[withDials] Injected dials into structured user message (${user.length} chars)`));
    }
    return { system: base.system, user };
  }

  // ── Legacy plain string ───────────────────────────────────────────────────
  const final = injectDialsIntoString(base, dialsPrompt);
  if (shouldLog("debug")) {
    console.log(formatLogLine("debug", null, `[withDials] Injected dials into string prompt (${final.length} chars)`));
  }
  return final;
}