All modules live in src/. This document covers every module's public API, inputs, and outputs.
Master task loop. Consumes all other modules.
const { run } = require('./src/orchestrator');
await run({
goal: 'Add JWT authentication to the FastAPI backend',
type: 'python',
buildCommand: 'pytest',
maxBudgetUSD: 5.00,
maxTokens: 500000,
resume: false,
noGit: false,
targetDir: '/path/to/project',
liteOnly: false,
debateOnly: false,
apply: true,
dryRun: false,
autoCommit: false,
allowProtected: false,
allowISR: false,
confidenceThreshold: 70,
verbose: false, // true = dump raw model JSON in debate viewer
});Scores a potential change on a 0-100 scale.
const { classify } = require('./src/riskClassifier');
const result = classify({
filePaths: ['src/payments/processor.ts', 'src/db/transactions.ts'],
diffText: '--- a/src/payments/processor.ts\n+++ b/src/payments/processor.ts\n...',
taskDescription: 'Refactor payment rollback handler',
allowProtected: false,
});
// result:
{
risk_score: 75,
risk_level: 'high', // 'low' | 'medium' | 'high' | 'critical'
triggers: ['Critical path keywords (transaction/rollback)', 'Protected path: src/payments'],
recommended_mode: 'debate', // 'lite' | 'debate'
}Converts classifier output + CLI flags into a mode decision.
const { selectMode } = require('./src/modeSelector');
const { mode, reason } = selectMode(classifierResult, {
liteOnly: false,
debateOnly: false,
});
// mode: 'lite' | 'debate'
// reason: human-readable explanationSingle OpenAI pass producing a unified diff.
const liteReviewer = require('./src/liteReviewer');
const result = await liteReviewer.runLite(
goal, // string — task description
codeContext, // string — source file contents (truncated)
arch, // string — latest architecture decision summary
constraints, // string[] — project constraints from memory
recentErrors, // string[] — last N build errors
budget, // BudgetManager instance
riskInfo // riskClassifier.classify() result
);
// result:
{
summary: 'Add NULL guard before packet dispatch',
root_cause: 'Missing bounds check on payload length',
plan: '1. Add guard at line 42\n2. Update unit test',
patch: '--- a/src/auth/middleware.ts\n+++ b/src/auth/middleware.ts\n@@...',
risk_level: 'medium',
confidence: 82,
justification: 'Change is isolated to receive path, no struct modification',
}Returns a degraded result (low confidence, empty patch) on JSON parse failure instead of throwing.
4-phase adversarial debate pipeline.
const debateOrchestrator = require('./src/debateOrchestrator');
const result = await debateOrchestrator.runDebate(
task, // TaskManager task object { id, description }
codeContext, // string
arch, // string
constraints, // string[]
recentErrors, // string[]
budget, // BudgetManager instance
targetDir, // string — project root dir (for saving debate record)
{
confidenceThreshold: 70,
allowProtected: false,
allowISR: false,
riskClassification: riskInfo,
verbose: false, // true = debateViewer dumps raw JSON per phase
}
);
// result:
{
patch: '--- a/src/payments/processor.ts\n+++ b/src/payments/processor.ts\n@@...',
blocked: false,
blockReason: null,
approved: true, // false if user said 'n' at the debateViewer prompt
consensusResult: { allow_apply, allow_commit, reason, final_confidence, ... },
debateRecord: { phase1_proposal, phase2_critique, phase3_rebuttal, phase4_audit, ts, taskId },
}Phase budget tracking: each phase calls budget.startPhase('debate-p1') through debate-p4.
debateViewer.js is automatically invoked inside runDebate() — no separate call needed.
Full-featured Debate Viewer CLI UI. Renders structured, color-coded terminal panels for each debate phase. Called internally by debateOrchestrator.runDebate() but also usable standalone.
const debateViewer = require('./src/debateViewer');
// Must be called before any show* functions:
debateViewer.setVerbose(true); // print raw JSON after each phase
// 1. Phase overview panel (risk box)
debateViewer.showSessionOpen(riskInfo, taskId);
// riskInfo: { risk_score, risk_level, triggers }
// 2. GPT Proposal panel
debateViewer.showPhase1(proposal);
// proposal: critiqueParser.parseProposal() output
// 3. Claude Challenge panel (color-coded by severity)
debateViewer.showPhase2(critique);
// critique: critiqueParser.parseCritique() output
// 4. GPT Defense panel + optional patch diff
debateViewer.showPhase3(rebuttal, proposal);
// rebuttal: rebuttalEngine.runRebuttal() output
// proposal: phase-1 output (for v1 patch comparison)
// 5. Final Audit panel
debateViewer.showPhase4(audit);
// audit: critiqueParser.parseFinalAudit() output
// 6. Consensus panel
debateViewer.showConsensus(consensusOut);
// consensusOut: consensus.fromDebate() output
// 7. Risk heatmap (ASCII bars per-file)
debateViewer.showRiskHeatmap(proposal, rebuttal, critique);
// 8. Patch comparison panel (v1 vs v2)
debateViewer.showPatchComparison(v1PatchString, v2PatchString);
// 9. Human approval prompt — returns boolean
const approved = debateViewer.promptApply();
// Prints: "Apply patch? (y/n)"
// Returns: true = apply, false = abort
// 10. Persist session to <targetDir>/debates/
const { sessionFile, v1File, v2File } = debateViewer.persistSession(debateRecord, targetDir);
// Writes:
// debates/<ts>_session.json
// debates/<ts>_patch_v1.diff
// debates/<ts>_patch_v2.diff (only if v2 differs from v1)Color coding:
CRITICALfindings →chalk.bold.redHIGHfindings →chalk.redMEDIUM/ design flaws →chalk.yellow- Safe / passing →
chalk.green - Risk score ≥ 75 → red, ≥ 50 → yellow, < 50 → green
- Confidence ≥ 80% → green, ≥ 60% → yellow, < 60% → red
Dependencies: chalk (v4), cli-table3, diff, readline-sync
Parses and normalises AI-generated JSON for each debate phase. All parsers return a _parseOk boolean and fall back to safe defaults rather than throwing — the fallback object always contains the same fields as the success object (arrays default to [], strings to '', numbers to fallback values) so downstream code never hits undefined.length.
const parser = require('./src/critiqueParser');
parser.parseProposal(rawJSON)
// Phase 1 → { claim, plan_summary, reasoning, risks_identified, risks, patch, confidence, _parseOk }
parser.parseCritique(rawJSON)
// Phase 2 → { claim, reasoning, risks, critical_findings, design_flaws, unsafe_changes,
// missing_tests, recommended_changes, confidence, _parseOk }
// On complete JSON failure: all arrays = [], confidence = 0, _parseOk = false
parser.parseRebuttal(rawJSON)
// Phase 3 → { rebuttal_summary, addressed_items, updated_patch, residual_risks, confidence, _parseOk }
parser.parseFinalAudit(rawJSON)
// Phase 4 → { final_risk_level, remaining_issues, approval_recommendation, confidence, _parseOk }Handles Phase 3 — Proposer defends against the Critic's challenge. Uses the same proposer provider as Phase 1.
const rebuttalEngine = require('./src/rebuttalEngine');
const rebuttal = await rebuttalEngine.runRebuttal(
proposal, // Phase 1 output
critique, // Phase 2 output
taskDescription,
codeContext,
budget
);
// rebuttal:
{
rebuttal_summary: 'Fixed ordering issue; justified NULL check',
addressed_items: [{ item, action: 'changed'|'justified', notes }],
updated_patch: '--- a/src/payments/processor.ts ...',
residual_risks: ['Memory pressure at high message rate'],
confidence: 88,
}Normalises LITE or DEBATE output into a single CONSENSUS_OUTPUT object.
const consensus = require('./src/consensus');
// From LITE:
const out = consensus.fromLite(liteResult, riskInfo, guardrailResult, { confidenceThreshold: 70 });
// From DEBATE:
const out = consensus.fromDebate(finalAudit, riskInfo, guardrailResult, { confidenceThreshold: 70 });
// Display in terminal:
consensus.display(out);Output schema:
{
allow_apply: boolean,
allow_commit: boolean,
reason: string,
final_confidence: number,
mode_used: 'lite' | 'debate',
triggers: string[],
risk_level: string,
low_confidence_mode: boolean,
blocked_reasons: string[],
}const patchApplier = require('./src/patchApplier');
// Apply a unified diff to disk via git apply:
const result = patchApplier.applyDiff(diffText, targetDir, patchesDir, taskId);
// result: { applied: boolean, error: string|null, diffFile: string }
// Save diff without applying (audit trail):
const filePath = patchApplier.saveDiff(diffText, patchesDir, taskId);
// Log affected files to console (preview):
patchApplier.previewDiff(diffText);
// Legacy full-file write (kept for compatibility):
patchApplier.writeFiles(files, targetDir);
patchApplier.previewFiles(files);applyDiff() saves the diff first, then applies it through a three-step fallback chain:
git apply --checkdry-run →git apply(hunk headers are sanitised before this step to fix AI-generated line-count errors)git apply --3way(merge-style apply)- Direct full-file reconstruction from the diff content
Only if all three steps fail does it return { applied: false }.
const guardrails = require('./src/guardrails');
guardrails.checkPatch(patchText, { allowISR, allowEmptyPatch })
// → { safe: boolean, violations: string[] }
// Note: consecutive-deletion and per-file deletion-ratio checks are automatically
// skipped for web/script/markup file extensions (.css, .html, .js, .ts, .py, etc.)
// The check uses the file path from each hunk's "--- a/..." line.
guardrails.checkPaths(filePaths, allowProtected)
// → { safe: boolean, violations: string[] }
guardrails.checkFiles(newFiles, originalFiles)
// → { safe: boolean, violations: string[] }
// Constants:
guardrails.MAX_PATCH_LINES // 400
guardrails.MAX_DELETION_RATIO // 0.30
guardrails.MAX_CONSECUTIVE_DELETIONS // 50 (not enforced on safe-extension files)
guardrails.PROTECTED_PATH_PREFIXES // string[]
guardrails.SAFE_EXTENSIONS // Set<string> — exempt file extensionsconst buildRunner = require('./src/buildRunner');
const result = buildRunner.runBuild(command, targetDir);
// result: { success: boolean, stdout: string, stderr: string, exitCode: number }
const errorText = buildRunner.extractErrors(stdout, stderr);
// Returns condensed error summary stringconst git = require('./src/gitManager');
git.ensureRepo(targetDir)
git.createBranch(branchName, targetDir)
git.stageFiles(filePathsArray, targetDir) // empty array = git add -A
git.commit(message, targetDir)
git.awaitApproval(question, autoApprove) // returns booleanAll debate phases use the provider abstraction from src/providers/. The proposer and critic are selected at runtime via PROPOSER_PROVIDER / CRITIC_PROVIDER env vars. Lite mode continues to call the legacy openai.js / claude.js helpers directly.
const { createProvider } = require('./src/providers/providerFactory');
// String form:
const provider = createProvider('openai'); // → OpenAIProvider
const provider = createProvider('claude'); // → ClaudeProvider
const provider = createProvider('opencode'); // → OpenCodeProvider
// Object descriptor form (model override):
const provider = createProvider({ provider: 'opencode', model: 'kimi-k2.5-free' });Abstract base class. All providers extend it and implement generate(prompt, options).
// interface:
async generate(prompt, options = {}) → { content: string, tokensUsed: number }LLMProvider for the OpenCode Zen API (OpenAI-compatible, /zen/v1/chat/completions).
const { OpenCodeProvider, ALLOWLIST } = require('./src/providers/OpenCodeProvider');
const provider = new OpenCodeProvider({ baseUrl, apiKey, model });
await provider.generate(prompt, {
model, // per-call override (must be in ALLOWLIST or OPENCODE_ALLOW_ANY_MODEL=1)
systemPrompt, // or options.system
temperature, // default 0.2
max_tokens, // default 4096
budgetManager,
debateId, // for llm_invalid_json event
phase,
});Built-in allowlist (ALLOWLIST Set): glm-5-free, minimax-m2.5-free, kimi-k2.5-free, big-pickle.
Set OPENCODE_ALLOW_ANY_MODEL=1 to bypass.
Emits logger.logEvent({ event: 'llm_invalid_json', ... }) when the response content is not parseable JSON, so orchestrators can log partial-parse warnings.
Isolated container for a single debate run. Resolves provider instances from env vars.
const DebateSession = require('./src/core/DebateSession');
const session = new DebateSession({ budget });
session.proposerProvider // LLMProvider instance for PROPOSER_PROVIDER
session.criticProvider // LLMProvider instance for CRITIC_PROVIDER
session.proposerModel // string — PROPOSER_MODEL env value
session.criticModel // string — CRITIC_MODEL env value
session.id // UUID for this debate run
session.meta // { proposerProvider, proposerModel, criticProvider, criticModel }Legacy helpers (still used by liteReviewer.js and other non-debate code).
const openai = require('./src/openai');
// Returns { parsed: object, content: string } — throws on budget exceeded
const { parsed, content } = await openai.completeJSON(systemPrompt, userPrompt, budget);
// Plain string completion:
const text = await openai.complete(systemPrompt, userPrompt, budget);Also exports: class OpenAIProvider extends LLMProvider — imported by providerFactory.js.
const claude = require('./src/claude');
const text = await claude.complete(systemPrompt, userPrompt, budget);
const { parsed, content } = await claude.completeJSON(systemPrompt, userPrompt, budget);Also exports: class ClaudeProvider extends LLMProvider — imported by providerFactory.js.
Both legacy clients implement:
- Automatic retry with exponential backoff on rate-limit errors
- Pre-call budget check (throws
Error('Budget exceeded')if limit reached) - Token + cost recording via
budget.record()
const BudgetManager = require('./src/budgetManager');
const budget = new BudgetManager(maxUSD, maxTokens);
budget.record(model, promptTokens, completionTokens) // cost computed from built-in PRICING table
budget.checkBudget(estimatedTokens) // throws if over limit
budget.hasHeadroom(estimatedUSD, estimatedTokens) // boolean
budget.estimateDebateCost() // rough USD estimate for 4-phase debate
budget.startPhase(phaseName)
budget.endPhase()
budget.getSummary()
// returns:
// {
// totalTokens, promptTokens, completionTokens,
// totalCostUSD,
// openaiCostUSD, // cumulative spend on OpenAI models only
// claudeCostUSD, // cumulative spend on Claude models only
// remaining, percentUsed
// }
budget.phaseSummary() // [{ phase, promptTokens, completionTokens, costUSD, calls }]
budget.toJSON()
budget.restoreFrom(savedState)const logger = require('./src/logger');
logger.banner()
logger.section(title)
logger.step(n, total, description)
logger.success(message)
logger.warn(message)
logger.error(message)
logger.info(message)
logger.hr()
logger.budget(summary)
// New in v2.0:
logger.riskScore(riskInfo)
logger.modeDecision(mode, reason, riskInfo)
logger.phaseHeader(mode, phase, total, description)
logger.consensusResult(consensus)const Memory = require('./src/memory');
const mem = new Memory(filePath);
mem.load()
mem.addArchitectureDecision(summary, details)
mem.addConstraint(constraint)
mem.addError(taskId, errorText, relatedFile)
mem.getArchitectureDecisions() // array, newest last
mem.getConstraints() // string[]
mem.getRecentErrors(n) // last N error objectsconst StateTracker = require('./src/stateTracker');
const state = new StateTracker(filePath);
state.load()
state.init(goal, type, buildCommand)
state.nextIteration() // returns new iteration number
state.setCurrentTask(taskId)
state.completeTask(taskId)
state.failTask(taskId)
state.setBuildSuccess(bool)
state.setLastPatch(filePath)
state.updateBudget(budgetJSON)
state.getState() // full state objectconst TaskManager = require('./src/taskManager');
const tasks = new TaskManager(memory);
tasks.initTasks(taskListArray)
tasks.all() // all tasks
tasks.getNextTask() // next runnable task (deps satisfied)
tasks.markInProgress(id)
tasks.markDone(id)
tasks.markFailed(id, reason)
tasks.allDone() // boolean
tasks.summary() // { total, done, failed, inProgress, pending }