← back to Kickbacks Ai Upstream

src/activation/diagnose.ts

139 lines

import * as vscode from "vscode";
import type { AdapterDiagnostics, TargetAdapter } from "../adapters/types";
import { buildVersion, buildLabel, BUILD_TS } from "../buildinfo";
import { debugEnabled, LOG_PATH } from "../log";

/** Plain-English verdict + remedy for a Claude Code diagnosis. This is the line
 *  that ends the guessing: it maps the raw flags to "what's wrong and what to
 *  do", so a screenshot of the report is self-explanatory. */
export function interpret(d: AdapterDiagnostics): string {
  if (d.compatible) {
    return d.isPatched
      ? "VERDICT: OK — Claude Code is patchable and the ad block is live."
      : "VERDICT: OK — Claude Code is patchable (block not applied yet this session).";
  }
  if (!d.targetExists)
    return "VERDICT: Claude Code not found at the target path. Is the Claude Code "
      + "extension installed? (No action for Kickbacks.)";
  if (d.live.bareVerbPresent && !d.live.hasArray)
    return "VERDICT: the verb words exist but NOT in a matchable array — Claude "
      + "Code likely changed its bundle format. FIX: update the adapter's array "
      + "regex/anchors (this is a real CC change, send this report to the dev).";
  if (!d.live.bareVerbPresent && !d.backup.hasArray)
    return "VERDICT: the verb array is missing from the live file and no good "
      + "backup exists — the file was stripped/corrupted (an old patch). FIX: "
      + "reinstall or update Claude Code to restore its original file, then reload.";
  if (d.backup.exists && !d.backup.hasArray && d.live.hasArray)
    return "VERDICT: stale backup but the live file is fine — should self-heal on "
      + "the next apply. FIX: update Kickbacks to the latest build and reload.";
  return "VERDICT: incompatible — verb array not located. Send this report to the dev.";
}

/** Codex inputs for the report. `adapter` is located for DIAGNOSIS regardless
 *  of the serving policy (null = no Codex install on this machine); `policy`
 *  is the boot-time codexFallback verdict + its inputs. Keeping both lets the
 *  report explain the BUG-001 dual-install case — "Codex installed, serving
 *  deliberately OFF" — instead of silently omitting the section. */
export interface CodexDiagnostics {
  adapter: TargetAdapter | null;
  policy: { discoveryEnabled: boolean; optIn: boolean; optOut: boolean;
            claudeCompatible: boolean };
}

/** Plain-English verdict for the Codex section. The dual-install case is the
 *  one users actually hit (BUG-001): Codex installed alongside a working
 *  Claude Code → serving is opt-in by design, not broken. */
export function interpretCodex(
  p: CodexDiagnostics["policy"], compatible: boolean,
): string {
  if (p.optOut)
    return "VERDICT: Codex ad-serving is explicitly disabled "
      + "(~/.vibe-ads/codex.disabled or KICKBACKS_CODEX=0).";
  if (!p.discoveryEnabled)
    return "VERDICT: Codex detected but Codex ad-serving is OFF — it is "
      + "opt-in on machines with a working Claude Code (expected, not a bug). "
      + "To serve ads in Codex too: set KICKBACKS_CODEX=1 or create "
      + "~/.vibe-ads/codex.enabled, then reload.";
  return compatible
    ? "VERDICT: OK — Codex is a live ad target this session."
    : "VERDICT: Codex targeted but incompatible — send this report to the dev.";
}

/** Render the full copyable diagnostic report. Pure (no I/O) for easy testing. */
export function formatDiagnostics(
  cc: TargetAdapter | null, codex: CodexDiagnostics | null,
): string {
  const L: string[] = [];
  L.push("=== Kickbacks Diagnostics ===");
  L.push(`extension version: ${buildVersion()}`);
  L.push(`build: ${buildLabel()}  (BUILD_TS=${BUILD_TS || "dev"})`);
  L.push(`debug enabled: ${debugEnabled()}`);
  L.push(`debug log: ${LOG_PATH}`);
  L.push("");
  L.push("--- Claude Code ---");
  const d = cc?.diagnose?.();
  if (!d) {
    L.push("(no diagnose() available)");
  } else {
    L.push(`target: ${d.target}`);
    L.push(`target exists: ${d.targetExists}`);
    L.push(`CC version: ${d.version}`);
    L.push(`PREFLIGHT compatible: ${d.compatible}`);
    if (d.reason) L.push(`preflight reason: ${d.reason}`);
    L.push(`patch live (isPatched): ${d.isPatched}`);
    L.push(`backup: exists=${d.backup.exists} hasArray=${d.backup.hasArray} `
      + `hasBlock=${d.backup.hasBlock}`);
    if (d.backup.path) L.push(`  backup path: ${d.backup.path}`);
    L.push(`live: hasArray=${d.live.hasArray} bareVerbPresent=${d.live.bareVerbPresent}`);
    L.push("");
    L.push(interpret(d));
  }
  if (codex) {
    L.push("");
    L.push("--- Codex ---");
    if (!codex.adapter) {
      L.push("Codex (openai.chatgpt) not found on this machine.");
    } else {
      const pf = (() => {
        try { return codex.adapter.preflight(); }
        catch { return { compatible: false, reason: "preflight threw",
                         version: null as string | null }; }
      })();
      L.push(`compatible: ${pf.compatible}`
        + `${pf.reason ? ` reason=${pf.reason}` : ""} version=${pf.version}`);
      const p = codex.policy;
      L.push(`serving policy: ${p.discoveryEnabled ? "ON" : "OFF"} `
        + `(optIn=${p.optIn} optOut=${p.optOut} `
        + `claudeCompatible=${p.claudeCompatible})`);
      L.push(interpretCodex(p, pf.compatible));
    }
  }
  return L.join("\n");
}

/** Register the `Kickbacks: Diagnose` command (+ legacy alias). Opens the report
 *  in an untitled editor AND copies it to the clipboard so the user can paste it
 *  straight back. Registered early in activation so it works even when the build
 *  is incompatible — which is exactly when it's needed. */
export function registerDiagnoseCommand(
  cc: TargetAdapter | null, codex: CodexDiagnostics | null,
): vscode.Disposable[] {
  const run = async (): Promise<void> => {
    const report = formatDiagnostics(cc, codex);
    try { await vscode.env.clipboard.writeText(report); } catch { /* best-effort */ }
    try {
      const doc = await vscode.workspace.openTextDocument(
        { content: report, language: "text" });
      await vscode.window.showTextDocument(doc, { preview: false });
    } catch { /* best-effort */ }
    try {
      await vscode.window.showInformationMessage?.(
        "Kickbacks diagnostics copied to clipboard.");
    } catch { /* best-effort */ }
  };
  return [
    vscode.commands.registerCommand("kickbacks.diagnose", run),
    vscode.commands.registerCommand("vibe-ads.diagnose", run),
  ];
}