Skip to content
Visibility internal Owner _ Approver _ Created _ Updated _

optimize.ts



FieldValue
TypeTypeScript
SourceForge/Skills/Forge_Optimizer/scripts/optimize.ts
ParentForge
GitHubForge/Skills/Forge_Optimizer/scripts/optimize.ts

This page is auto-generated. Edit the source to change the content.



#!/usr/bin/env tsx
/**
 * optimize.ts — Analyze a past Forge conversation for inefficiencies.
 *
 * Connects to MongoDB, builds a transcript, sends it to GPT-5.4 with
 * Forge_Chat_Prompt.md context, and prints a structured analysis.
 *
 * Usage:
 *   npx tsx optimize.ts "Conversation Title"
 *   npx tsx optimize.ts --latest
 *   npx tsx optimize.ts --list
 *   npx tsx optimize.ts --project <ProjectName> [--model <model>]
 *
 * In project mode, saves the report to {Project}_Optimizer_Report.md
 * in the project folder.
 *
 * Exit codes:
 *   0 — Analysis printed successfully
 *   1 — Error
 */

import { MongoClient } from 'mongodb';
import pg from 'pg';
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { execSync } from 'child_process';

// ── Configuration ──────────────────────────────────────────────────────────────

const MONGO_URI = process.env['MONGO_URI'];
const DATABASE = 'test';
const DEFAULT_MODEL = 'gpt-5.4';
const PROJECT_MODEL = 'glm-5.1';
const MAX_OUTPUT_PER_TOOL = 1500; // chars of tool output to include

const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
const SKILL_DIR = resolve(SCRIPT_DIR, '..');
const PROMPT_PATH = resolve(SKILL_DIR, 'references', 'extraction_prompt.md');
const FORGE_MD_PATH = resolve(SKILL_DIR, '..', '..', 'Configs', 'Agents', 'Forge_Chat_Prompt.md');

// Patterns to redact from transcripts
const REDACT_PATTERNS: RegExp[] = [
  /(MONGO_URI|OPENAI_API_KEY|API_KEY|TOKEN|SECRET|PASSWORD|PRIVATE_KEY)=[^\s\x00"]+/g,
  /mongodb:\/\/[^\s"]+/g,
  /sk-[a-zA-Z0-9_-]{20,}/g,
];

// ── Types ──────────────────────────────────────────────────────────────────────

interface Conversation {
  conversationId: string;
  title?: string;
  updatedAt?: unknown;
}

interface Message {
  sender?: string;
  text?: string;
  content?: ContentItem[];
}

interface ContentItem {
  type?: string;
  text?: string;
  tool_call?: {
    name?: string;
    args?: unknown;
    output?: unknown;
  };
}

interface OpenAIResponse {
  choices: Array<{ message: { content: string } }>;
}

// ── MongoDB ────────────────────────────────────────────────────────────────────

async function connect(): Promise<{ client: MongoClient; db: ReturnType<MongoClient['db']> }> {
  if (!MONGO_URI) {
    console.error('Error: MONGO_URI not found in environment');
    process.exit(1);
  }
  const client = new MongoClient(MONGO_URI);
  await client.connect();
  return { client, db: client.db(DATABASE) };
}

async function findConversation(
  db: ReturnType<MongoClient['db']>,
  title?: string,
  latest?: boolean,
): Promise<Conversation | null> {
  const coll = db.collection<Conversation>('conversations');

  if (title) {
    const doc = await coll.findOne({ title });
    if (doc) return doc;

    // Partial match fallback
    const docs = await coll
      .find(
        { title: { $regex: title, $options: 'i' } },
        { projection: { conversationId: 1, title: 1, updatedAt: 1 } },
      )
      .sort({ updatedAt: -1 })
      .limit(5)
      .toArray();

    if (docs.length > 0) {
      console.log(`No exact match for '${title}'. Partial matches:`);
      for (const d of docs) console.log(`  ${d.title}  (${d.updatedAt})`);
    } else {
      console.log(`No conversation found matching '${title}'`);
    }
    return null;
  }

  if (latest) {
    const docs = await coll
      .find({}, { projection: { conversationId: 1, title: 1, updatedAt: 1 } })
      .sort({ updatedAt: -1 })
      .limit(5)
      .toArray();

    if (docs.length < 2) {
      console.log('Not enough conversations to pick a non-current one');
      return null;
    }

    const target = docs[1]; // skip most recent (likely current session)
    const doc = await coll.findOne({ conversationId: target.conversationId });
    console.log(`Selected: ${doc?.title ?? 'Untitled'} (${doc?.updatedAt})`);
    return doc;
  }

  return null;
}

async function listConversations(
  db: ReturnType<MongoClient['db']>,
  limit = 15,
): Promise<void> {
  const docs = await db
    .collection<Conversation>('conversations')
    .find({}, { projection: { title: 1, updatedAt: 1 } })
    .sort({ updatedAt: -1 })
    .limit(limit)
    .toArray();

  console.log('Recent conversations:');
  for (const d of docs) {
    console.log(`  ${(d.title ?? 'Untitled').padEnd(55)} ${d.updatedAt}`);
  }
}

// ── Transcript ─────────────────────────────────────────────────────────────────

function redact(text: string): string {
  let result = text;
  for (const pattern of REDACT_PATTERNS) {
    result = result.replace(pattern, '[REDACTED]');
  }
  return result;
}

async function buildTranscript(
  db: ReturnType<MongoClient['db']>,
  conversation: Conversation,
): Promise<string> {
  const msgs = await db
    .collection<Message>('messages')
    .find({ conversationId: conversation.conversationId })
    .sort({ createdAt: 1 })
    .toArray();

  const parts: string[] = [];

  for (const msg of msgs) {
    const role = msg.sender ?? 'unknown';
    const text = msg.text ?? '';
    const content = msg.content ?? [];

    const lines: string[] = [];

    if (text) lines.push(redact(text));

    for (const item of content) {
      if (item.type === 'tool_call') {
        const tc = item.tool_call ?? {};
        const name = tc.name ?? 'unknown';
        const args = redact(String(tc.args ?? ''));
        let output = redact(String(tc.output ?? ''));
        if (output.length > MAX_OUTPUT_PER_TOOL) {
          output = output.slice(0, MAX_OUTPUT_PER_TOOL) + '\n[...truncated...]';
        }
        lines.push(`[TOOL CALL] ${name}\nArgs: ${args}\nOutput: ${output}`);
      } else if (item.type === 'text') {
        const txt = item.text ?? '';
        if (txt) lines.push(redact(txt));
      }
    }

    if (lines.length > 0) {
      parts.push(`### ${role}\n` + lines.join('\n\n'));
    }
  }

  return parts.join('\n\n---\n\n');
}

// ── LLM Analysis ──────────────────────────────────────────────────────────────

function getApiKey(): string {
  const key = process.env['OPENAI_API_KEY'];
  if (!key) {
    console.error('Error: OPENAI_API_KEY not found');
    process.exit(1);
  }
  return key;
}

function loadPrompt(transcript: string): string {
  const raw = readFileSync(PROMPT_PATH, 'utf8');
  // Strip YAML frontmatter (between first two --- lines)
  const sections = raw.split('---\n');
  const body = sections.length >= 3 ? sections.slice(2).join('---\n') : raw;
  return body.replace('{transcript}', transcript);
}

async function analyze(transcript: string, apiKey: string, model: string): Promise<string> {
  const prompt = loadPrompt(transcript);
  const forgeMd = readFileSync(FORGE_MD_PATH, 'utf8');

  const messages = [
    {
      role: 'system',
      content:
        'You are an expert AI operations analyst reviewing agentic execution ' +
        'transcripts. You have the complete Forge operating rules below. ' +
        "Your job is to find real problems — not to fill categories with noise.\n\n" +
        'Forge_Chat_Prompt.md:\n' +
        forgeMd,
    },
    { role: 'user', content: prompt },
  ];

  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      model,
      messages,
      max_completion_tokens: 2000,
      temperature: 0.2,
    }),
    signal: AbortSignal.timeout(120_000),
  });

  if (!response.ok) {
    throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`);
  }

  const result = (await response.json()) as OpenAIResponse;
  return result.choices[0].message.content;
}

// ── Project mode ────────────────────────────────────────────────────────────

async function fetchProjectConversations(
  pgPool: pg.Pool,
  projectName: string,
): Promise<Array<{ conversationId: string; agentId: string; phase: string | null }>> {
  const result = await pgPool.query(
    `SELECT DISTINCT "conversationId", "agentId", phase FROM "AgentJob" WHERE project = $1 ORDER BY "conversationId"`,
    [projectName],
  );
  return result.rows;
}

async function buildProjectTranscript(
  db: ReturnType<MongoClient['db']>,
  conversationIds: string[],
): Promise<string> {
  const parts: string[] = [];
  for (const convId of conversationIds) {
    const conv = await db.collection('conversations').findOne({ conversationId: convId });
    const title = (conv as any)?.title ?? 'Untitled';
    parts.push(`## Conversation: ${title} (${convId})`);
    const transcript = await buildTranscript(db, conv as Conversation);
    parts.push(transcript);
  }
  return parts.join('\n\n---\n\n');
}

// ── Project report ──────────────────────────────────────────────────────────

function findProjectFolder(projectName: string): string | null {
  const repoRoot = resolve(SCRIPT_DIR, '..', '..', '..', '..'); // scripts → repo root
  try {
    const result = execSync(
      `find "${repoRoot}" -path "*/Projects/*" -name "${projectName}_Phase.md" -not -path "*/Archived/*" -not -path "*/.internal/*" -not -path "*/node_modules/*" -not -path "*/.generated/*" -not -path "*/dist/*"`,
      { encoding: 'utf8', timeout: 10_000 },
    ).trim();
    if (result) {
      return dirname(result.split('\n')[0]!);
    }
  } catch {
    // find returned nothing or errored
  }
  return null;
}

function saveProjectReport(projectName: string, analysis: string): string | null {
  const projectFolder = findProjectFolder(projectName);
  if (!projectFolder) {
    console.log(`Warning: Could not locate project folder for "${projectName}" — report not saved to file`);
    return null;
  }

  const reportPath = resolve(projectFolder, `${projectName}_Optimizer_Report.md`);
  const today = new Date().toISOString().slice(0, 10);
  const runSection = `## Run — ${today}\n\n${analysis}`;

  if (existsSync(reportPath)) {
    // Append a new run section to the existing file
    const existing = readFileSync(reportPath, 'utf8');
    const updated = existing.trimEnd() + '\n\n---\n\n' + runSection + '\n';
    writeFileSync(reportPath, updated, 'utf8');
    console.log(`Appended optimizer report to: ${reportPath}`);
  } else {
    // Create new report file with frontmatter
    const content =
      `---\n` +
      `title: "${projectName} Optimizer Report"\n` +
      `status: published\n` +
      `visibility: internal\n` +
      `owner: "erik@uvilo.com"\n` +
      `created: "${today}"\n` +
      `updated: "${today}"\n` +
      `---\n\n` +
      `# ${projectName} Optimizer Report\n\n` +
      `Automated analysis of project conversations, generated by the Forge Optimizer.\n\n` +
      `${runSection}\n`;
    writeFileSync(reportPath, content, 'utf8');
    console.log(`Saved optimizer report to: ${reportPath}`);
  }

  return reportPath;
}

// ── Main ───────────────────────────────────────────────────────────────────────

async function main(): Promise<void> {
  const args = process.argv.slice(2);

  if (args.length === 0) {
    console.log('Usage: npx tsx optimize.ts <title> | --latest | --list | --project <name> [--model <model>]');
    process.exit(1);
  }

  // Parse arguments
  let projectName: string | undefined;
  let overrideModel: string | undefined;
  let titleArg: string | undefined;
  let isLatest = false;
  let isList = false;

  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--project' && args[i + 1]) {
      projectName = args[++i];
    } else if (args[i] === '--model' && args[i + 1]) {
      overrideModel = args[++i];
    } else if (args[i] === '--latest') {
      isLatest = true;
    } else if (args[i] === '--list') {
      isList = true;
    } else if (!args[i]!.startsWith('--')) {
      titleArg = args[i];
    }
  }

  const { client, db } = await connect();

  try {
    if (isList) {
      await listConversations(db);
      return;
    }

    // Project mode
    if (projectName) {
      const model = overrideModel ?? PROJECT_MODEL;
      console.log(`Project mode: ${projectName}, model: ${model}`);

      const forgeDbUrl = process.env['FORGE_DB_URL'];
      if (!forgeDbUrl) {
        console.error('Error: FORGE_DB_URL not found in environment');
        process.exit(1);
      }
      const pgPool = new pg.Pool({ connectionString: forgeDbUrl });

      try {
        const jobs = await fetchProjectConversations(pgPool, projectName);
        if (jobs.length === 0) {
          console.log(`No AgentJob records found for project "${projectName}"`);
          process.exit(0);
        }
        console.log(`Found ${jobs.length} conversation(s) for project "${projectName}"`);

        const conversationIds = jobs.map((j) => j.conversationId);
        const transcript = await buildProjectTranscript(db, conversationIds);
        if (!transcript.trim()) {
          console.error('Error: empty project transcript');
          process.exit(1);
        }
        console.log(`Project transcript: ${transcript.length} chars, sending to ${model}...`);

        const apiKey = getApiKey();
        const analysis = await analyze(transcript, apiKey, model);
        console.log(`\n${analysis}`);

        // Save report to project folder as a workproduct file
        saveProjectReport(projectName, analysis);
      } finally {
        await pgPool.end();
      }
      return;
    }

    // Single-conversation mode
    const model = overrideModel ?? DEFAULT_MODEL;
    const conv =
      isLatest
        ? await findConversation(db, undefined, true)
        : await findConversation(db, titleArg);

    if (!conv) process.exit(1);

    const title = conv.title ?? 'Untitled';
    console.log(`Building transcript: ${title}`);

    const transcript = await buildTranscript(db, conv);
    if (!transcript.trim()) {
      console.error('Error: empty transcript');
      process.exit(1);
    }
    console.log(`Transcript: ${transcript.length} chars, sending to ${model}...`);

    const apiKey = getApiKey();
    const analysis = await analyze(transcript, apiKey, model);
    console.log(`\n${analysis}`);
  } finally {
    await client.close();
  }
}

main().catch((err) => {
  console.error(err instanceof Error ? err.message : String(err));
  process.exit(1);
});