@autobe/agent
Version:
AI backend server code generator
871 lines (870 loc) • 60.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.orchestrateAnalyze = void 0;
const core_1 = require("@agentica/core");
const uuid_1 = require("uuid");
const AutoBePreliminaryExhaustedError_1 = require("../../utils/AutoBePreliminaryExhaustedError");
const AutoBeTimeoutError_1 = require("../../utils/AutoBeTimeoutError");
const executeCachedBatch_1 = require("../../utils/executeCachedBatch");
const fillTocDeterministic_1 = require("./fillTocDeterministic");
const orchestrateAnalyzeExtractDecisions_1 = require("./orchestrateAnalyzeExtractDecisions");
const orchestrateAnalyzeScenario_1 = require("./orchestrateAnalyzeScenario");
const orchestrateAnalyzeSectionCrossFileReview_1 = require("./orchestrateAnalyzeSectionCrossFileReview");
const orchestrateAnalyzeWriteSection_1 = require("./orchestrateAnalyzeWriteSection");
const orchestrateAnalyzeWriteSectionPatch_1 = require("./orchestrateAnalyzeWriteSectionPatch");
const orchestrateAnalyzeWriteUnit_1 = require("./orchestrateAnalyzeWriteUnit");
const AutoBeAnalyzeProgrammer_1 = require("./programmers/AutoBeAnalyzeProgrammer");
const FixedAnalyzeTemplate_1 = require("./structures/FixedAnalyzeTemplate");
const buildConstraintConsistencyReport_1 = require("./utils/buildConstraintConsistencyReport");
const buildErrorCodeRegistry_1 = require("./utils/buildErrorCodeRegistry");
const buildHardValidators_1 = require("./utils/buildHardValidators");
const detectDecisionConflicts_1 = require("./utils/detectDecisionConflicts");
const detectProseConstraintConflicts_1 = require("./utils/detectProseConstraintConflicts");
const validateScenarioBasics_1 = require("./utils/validateScenarioBasics");
const ANALYZE_SCENARIO_MAX_RETRY = 2;
const ANALYZE_SECTION_FILE_MAX_RETRY = 5;
const ANALYZE_SECTION_FILE_MAX_REVIEW = 2;
const ANALYZE_SECTION_STAGNATION_MAX = 4;
const ANALYZE_DEBUG_LOG = process.env.AUTOBE_DEBUG_ANALYZE === "1";
const analyzeDebug = (message) => {
if (!ANALYZE_DEBUG_LOG)
return;
console.log(`[analyze-debug] ${new Date().toISOString()} ${message}`);
};
const orchestrateAnalyze = (ctx) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c;
// Initialize analysis state
const step = ((_b = (_a = ctx.state().analyze) === null || _a === void 0 ? void 0 : _a.step) !== null && _b !== void 0 ? _b : -1) + 1;
const startTime = new Date();
ctx.dispatch({
type: "analyzeStart",
id: (0, uuid_1.v7)(),
step,
created_at: startTime.toISOString(),
});
// Generate analysis scenario with pre-check + LLM review + retry loop
let scenario;
let scenarioFeedback;
for (let attempt = 0; attempt <= ANALYZE_SCENARIO_MAX_RETRY; attempt++) {
const rawScenario = yield (0, orchestrateAnalyzeScenario_1.orchestrateAnalyzeScenario)(ctx, {
feedback: scenarioFeedback,
});
if (rawScenario.type === "assistantMessage")
return ctx.assistantMessage(rawScenario);
// 1) Programmatic pre-check
const preCheck = (0, validateScenarioBasics_1.validateScenarioBasics)({
prefix: rawScenario.prefix,
actors: rawScenario.actors,
entities: rawScenario.entities,
});
if (!preCheck.valid && attempt < ANALYZE_SCENARIO_MAX_RETRY) {
analyzeDebug(`Scenario pre-check failed (attempt ${attempt}): ${preCheck.errors.join("; ")}`);
scenarioFeedback = `Programmatic validation failed:\n${preCheck.errors.join("\n")}`;
continue;
}
// Accept scenario directly (write agent self-reviews during rewrite loop)
analyzeDebug(`Scenario accepted (attempt ${attempt})`);
scenario = rawScenario;
ctx.dispatch(scenario);
break;
}
// Initialize per-file state
const fileStates = scenario.files.map((file) => ({
file,
moduleResult: null,
unitResults: null,
sectionResults: null,
}));
// Progress tracking for each stage
const moduleWriteProgress = {
total: scenario.files.length,
completed: 0,
};
const unitWriteProgress = {
total: 0,
completed: 0,
};
const sectionWriteProgress = {
total: 0,
completed: 0,
};
const perFileSectionReviewProgress = {
total: 0,
completed: 0,
};
const crossFileSectionReviewProgress = {
total: 1,
completed: 0,
};
// === STAGE 1: MODULE (deterministic — no LLM) ===
processStageModuleDeterministic(ctx, {
scenario,
fileStates,
moduleWriteProgress,
});
// === STAGE 2: UNIT (fixed units deterministic, dynamic units LLM) ===
yield processStageUnit(ctx, {
scenario,
fileStates,
unitWriteProgress,
});
// === STAGE 3: SECTION (01-05 only, TOC excluded) ===
yield processStageSection(ctx, {
scenario,
fileStates,
sectionWriteProgress,
perFileSectionReviewProgress,
crossFileSectionReviewProgress,
});
// === TOC FILL (deterministic — no LLM) ===
const expandedTemplate = (0, FixedAnalyzeTemplate_1.buildFixedAnalyzeExpandedTemplate)(((_c = scenario.features) !== null && _c !== void 0 ? _c : []));
const tocIndex = fileStates.findIndex((s) => s.file.filename === "00-toc.md");
let tocContent = null;
if (tocIndex >= 0) {
tocContent = (0, fillTocDeterministic_1.fillTocDeterministic)(ctx, {
scenario,
tocFileState: fileStates[tocIndex],
otherFileStates: fileStates.filter((_, i) => i !== tocIndex),
expandedTemplate,
});
}
// === ASSEMBLE ===
const files = [];
for (let fileIndex = 0; fileIndex < fileStates.length; fileIndex++) {
const state = fileStates[fileIndex];
// TOC uses flat content directly (no module/unit hierarchy)
const content = fileIndex === tocIndex
? tocContent
: (0, AutoBeAnalyzeProgrammer_1.assembleContent)(state.moduleResult, state.unitResults, state.sectionResults);
const module = (0, AutoBeAnalyzeProgrammer_1.assembleModule)(state.moduleResult, state.unitResults, state.sectionResults);
files.push(Object.assign(Object.assign({}, state.file), { title: state.moduleResult.title, summary: state.moduleResult.summary, content,
module }));
}
// Complete the analysis
return ctx.dispatch({
type: "analyzeComplete",
id: (0, uuid_1.v7)(),
actors: scenario.actors,
prefix: scenario.prefix,
files,
aggregates: ctx.getCurrentAggregates("analyze"),
step,
elapsed: new Date().getTime() - startTime.getTime(),
created_at: new Date().toISOString(),
});
});
exports.orchestrateAnalyze = orchestrateAnalyze;
// MODULE (deterministic — no LLM calls)
/**
* Generate module structure deterministically from FixedAnalyzeTemplate.
*
* No LLM calls needed — module titles, purposes, and structure are all derived
* from the fixed 6-file SRS template.
*/
function processStageModuleDeterministic(ctx, props) {
var _a, _b, _c;
const expandedTemplate = (0, FixedAnalyzeTemplate_1.buildFixedAnalyzeExpandedTemplate)(((_a = props.scenario.features) !== null && _a !== void 0 ? _a : []));
for (const [i, state] of props.fileStates.entries()) {
const template = expandedTemplate[i];
const moduleEvent = {
type: "analyzeWriteModule",
id: (0, uuid_1.v7)(),
title: `${props.scenario.prefix} — ${template.description}`,
summary: template.description,
moduleSections: template.modules.map((m) => ({
title: m.title,
purpose: m.purpose,
content: m.purpose,
})),
step: ((_c = (_b = ctx.state().analyze) === null || _b === void 0 ? void 0 : _b.step) !== null && _c !== void 0 ? _c : -1) + 1,
retry: 0,
total: props.fileStates.length,
completed: i + 1,
tokenUsage: {
total: 0,
input: { total: 0, cached: 0 },
output: {
total: 0,
reasoning: 0,
accepted_prediction: 0,
rejected_prediction: 0,
},
},
metric: {
attempt: 0,
success: 0,
consent: 0,
validationFailure: 0,
invalidJson: 0,
},
acquisition: { previousAnalysisSections: [] },
created_at: new Date().toISOString(),
};
state.moduleResult = moduleEvent;
ctx.dispatch(moduleEvent);
props.moduleWriteProgress.completed++;
}
}
// UNIT
/**
* Process the Unit stage for all files.
*
* Fixed-strategy modules get deterministic unit generation (no LLM).
* Dynamic-strategy modules (perEntity/perActor/perEntityGroup) use LLM. No
* cross-file unit review — Hard Validators at section stage handle
* consistency.
*/
function processStageUnit(ctx, props) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const promptCacheKey = (0, uuid_1.v7)();
const expandedTemplate = (0, FixedAnalyzeTemplate_1.buildFixedAnalyzeExpandedTemplate)(((_a = props.scenario.features) !== null && _a !== void 0 ? _a : []));
// Count total units needed for progress tracking
for (const [fileIndex, state] of props.fileStates.entries()) {
const template = expandedTemplate[fileIndex];
props.unitWriteProgress.total += template.modules.length;
void state; // used below
}
yield (0, executeCachedBatch_1.executeCachedBatch)(ctx, props.fileStates.map((state, fileIndex) => (cacheKey) => __awaiter(this, void 0, void 0, function* () {
// TOC is filled deterministically after all other files complete
if (state.file.filename === "00-toc.md")
return [];
const moduleResult = state.moduleResult;
const template = expandedTemplate[fileIndex];
analyzeDebug(`unit file-start fileIndex=${fileIndex} file="${state.file.filename}"`);
const unitResults = [];
for (let moduleIndex = 0; moduleIndex < moduleResult.moduleSections.length; moduleIndex++) {
const moduleTemplate = template.modules[moduleIndex];
const strategy = moduleTemplate.unitStrategy;
if (strategy.type === "fixed") {
// Deterministic unit generation — no LLM
const unitEvent = buildDeterministicUnitEvent(ctx, {
moduleIndex,
units: strategy.units,
progress: props.unitWriteProgress,
});
ctx.dispatch(unitEvent);
unitResults.push(unitEvent);
}
else {
// Dynamic units — expand from template, then LLM writes content+keywords
const unitStart = Date.now();
analyzeDebug(`unit module-start fileIndex=${fileIndex} file="${state.file.filename}" moduleIndex=${moduleIndex} strategy=${strategy.type}`);
try {
const unitEvent = yield (0, orchestrateAnalyzeWriteUnit_1.orchestrateAnalyzeWriteUnit)(ctx, {
scenario: props.scenario,
file: state.file,
moduleEvent: moduleResult,
moduleIndex,
progress: props.unitWriteProgress,
promptCacheKey: cacheKey,
retry: 0,
});
analyzeDebug(`unit module-done fileIndex=${fileIndex} file="${state.file.filename}" moduleIndex=${moduleIndex} unitCount=${unitEvent.unitSections.length} elapsedMs=${Date.now() - unitStart}`);
unitResults.push(unitEvent);
}
catch (e) {
if (e instanceof core_1.AgenticaValidationError ||
e instanceof AutoBePreliminaryExhaustedError_1.AutoBePreliminaryExhaustedError ||
e instanceof AutoBeTimeoutError_1.AutoBeTimeoutError) {
analyzeDebug(`unit module-skipped fileIndex=${fileIndex} file="${state.file.filename}" moduleIndex=${moduleIndex} error=${e.constructor.name} elapsedMs=${Date.now() - unitStart} — using fallback`);
const expandedUnits = (0, FixedAnalyzeTemplate_1.expandFixedAnalyzeTemplateUnits)(moduleTemplate, props.scenario.entities, props.scenario.actors);
const fallbackEvent = buildDeterministicUnitEvent(ctx, {
moduleIndex,
units: expandedUnits,
progress: props.unitWriteProgress,
});
ctx.dispatch(fallbackEvent);
unitResults.push(fallbackEvent);
}
else {
throw e;
}
}
}
}
state.unitResults = unitResults;
analyzeDebug(`unit file-done fileIndex=${fileIndex} file="${state.file.filename}"`);
return unitResults;
})), promptCacheKey);
});
}
/** Build a deterministic AutoBeAnalyzeWriteUnitEvent for fixed-strategy modules. */
function buildDeterministicUnitEvent(ctx, props) {
var _a, _b;
props.progress.completed++;
return {
type: "analyzeWriteUnit",
id: (0, uuid_1.v7)(),
moduleIndex: props.moduleIndex,
unitSections: props.units.map((u) => ({
title: u.titlePattern,
purpose: u.purposePattern,
content: u.purposePattern,
keywords: [...u.keywords],
})),
step: ((_b = (_a = ctx.state().analyze) === null || _a === void 0 ? void 0 : _a.step) !== null && _b !== void 0 ? _b : -1) + 1,
retry: 0,
total: props.progress.total,
completed: props.progress.completed,
tokenUsage: {
total: 0,
input: { total: 0, cached: 0 },
output: {
total: 0,
reasoning: 0,
accepted_prediction: 0,
rejected_prediction: 0,
},
},
metric: {
attempt: 0,
success: 0,
consent: 0,
validationFailure: 0,
invalidJson: 0,
},
acquisition: { previousAnalysisSections: [] },
created_at: new Date().toISOString(),
};
}
// SECTION
/**
* Process the Section stage for all files with 2-pass review.
*
* Flow:
*
* 1. Write sections for pending files in parallel
* 2. Pass 1: Per-file detailed review (parallel) — validates EARS format, value
* consistency, bridge blocks, intra-file deduplication
* 3. Pass 2: Cross-file lightweight review (single call) — validates terminology
* alignment, value consistency across files, naming conventions
* 4. Merge results from both passes — reject if either pass rejects
* 5. Retry only rejected files (max 3 attempts)
*/
function processStageSection(ctx, props) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
// Exclude TOC (00-toc.md) — it is filled deterministically after all files
const pendingIndices = new Set(props.fileStates
.map((s, i) => (s.file.filename === "00-toc.md" ? -1 : i))
.filter((i) => i >= 0));
let crossFileReviewCount = 0;
for (let attempt = 0; attempt < 15 /* AutoBeConfigConstant.ANALYZE_RETRY */ && pendingIndices.size > 0; attempt++) {
// Dynamically increase progress for retries (module-level granularity)
const pendingModuleCount = [...pendingIndices].reduce((sum, fi) => { var _a, _b, _c; return sum + ((_c = (_b = (_a = props.fileStates[fi]) === null || _a === void 0 ? void 0 : _a.unitResults) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 1); }, 0);
props.perFileSectionReviewProgress.total += pendingModuleCount;
if (attempt > 0) {
props.crossFileSectionReviewProgress.total++;
}
// Write sections for pending files in parallel
const pendingArray = [...pendingIndices];
const sectionFileBatches = chunkSectionFileIndices(pendingArray, computeSectionBatchSize({
attempt,
pendingCount: pendingArray.length,
}));
const promptCacheKey = (0, uuid_1.v7)();
// Build scenario entity name list for invention validation (P0-B)
const scenarioEntityNames = props.scenario.entities.map((e) => e.name);
for (const sectionBatch of sectionFileBatches)
yield (0, executeCachedBatch_1.executeCachedBatch)(ctx, sectionBatch.map((fileIndex) => (cacheKey) => __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f;
const state = props.fileStates[fileIndex];
const moduleResult = state.moduleResult;
const unitResults = state.unitResults;
analyzeDebug(`section file-start attempt=${attempt} fileIndex=${fileIndex} file="${state.file.filename}" batchSize=${sectionBatch.length}`);
// Build rejected module/unit lookup for selective regeneration
const rejectedSet = buildRejectedSet(state.rejectedModuleUnits);
const feedbackMap = buildFeedbackMap(state.rejectedModuleUnits);
// Increase write progress only for sections that will be regenerated
for (let mi = 0; mi < unitResults.length; mi++) {
const unitEvent = unitResults[mi];
for (let ui = 0; ui < unitEvent.unitSections.length; ui++) {
if (isSectionRejected(rejectedSet, mi, ui)) {
props.sectionWriteProgress.total++;
}
}
}
// Write sections, skipping approved ones on retry
const sectionResults = [];
for (let moduleIndex = 0; moduleIndex < unitResults.length; moduleIndex++) {
const unitEvent = unitResults[moduleIndex];
const sectionsForModule = [];
for (let unitIndex = 0; unitIndex < unitEvent.unitSections.length; unitIndex++) {
if (isSectionRejected(rejectedSet, moduleIndex, unitIndex)) {
const sectionStart = Date.now();
// Regenerate this section with targeted feedback
const targetedInfo = feedbackMap.get(`${moduleIndex}:${unitIndex}`);
const targetedFeedback = (_a = targetedInfo === null || targetedInfo === void 0 ? void 0 : targetedInfo.feedback) !== null && _a !== void 0 ? _a : state.sectionFeedback;
const targetedSectionIndices = (_b = targetedInfo === null || targetedInfo === void 0 ? void 0 : targetedInfo.sectionIndices) !== null && _b !== void 0 ? _b : null;
analyzeDebug(`section unit-start attempt=${attempt} fileIndex=${fileIndex} file="${state.file.filename}" moduleIndex=${moduleIndex} unitIndex=${unitIndex} targetSections=${targetedSectionIndices ? `[${targetedSectionIndices.join(",")}]` : "all"}`);
const previousSection = (_d = (_c = state.sectionResults) === null || _c === void 0 ? void 0 : _c[moduleIndex]) === null || _d === void 0 ? void 0 : _d[unitIndex];
let sectionEvent;
try {
sectionEvent =
previousSection && (targetedFeedback === null || targetedFeedback === void 0 ? void 0 : targetedFeedback.trim())
? yield (0, orchestrateAnalyzeWriteSectionPatch_1.orchestrateAnalyzeWriteSectionPatch)(ctx, {
scenario: props.scenario,
file: state.file,
moduleEvent: moduleResult,
unitEvent,
moduleIndex,
unitIndex,
previousSectionEvent: previousSection,
feedback: targetedFeedback,
progress: props.sectionWriteProgress,
promptCacheKey: cacheKey,
retry: attempt,
scenarioEntityNames,
sectionIndices: targetedSectionIndices,
})
: yield (0, orchestrateAnalyzeWriteSection_1.orchestrateAnalyzeWriteSection)(ctx, {
scenario: props.scenario,
file: state.file,
moduleEvent: moduleResult,
unitEvent,
allUnitEvents: unitResults,
moduleIndex,
unitIndex,
progress: props.sectionWriteProgress,
promptCacheKey: cacheKey,
feedback: targetedFeedback,
retry: attempt,
scenarioEntityNames,
});
}
catch (e) {
if (e instanceof core_1.AgenticaValidationError ||
e instanceof AutoBePreliminaryExhaustedError_1.AutoBePreliminaryExhaustedError ||
e instanceof AutoBeTimeoutError_1.AutoBeTimeoutError) {
analyzeDebug(`section unit-force-pass attempt=${attempt} fileIndex=${fileIndex} file="${state.file.filename}" moduleIndex=${moduleIndex} unitIndex=${unitIndex} error=${e.constructor.name} — ${previousSection ? "reusing previous" : "using placeholder"}`);
if (previousSection) {
sectionEvent = previousSection;
}
else {
sectionEvent = {
type: "analyzeWriteSection",
id: (0, uuid_1.v7)(),
moduleIndex,
unitIndex,
sectionSections: [],
acquisition: { previousAnalysisSections: [] },
tokenUsage: {
total: 0,
input: { total: 0, cached: 0 },
output: {
total: 0,
reasoning: 0,
accepted_prediction: 0,
rejected_prediction: 0,
},
},
metric: {
attempt: 0,
success: 0,
consent: 0,
validationFailure: 0,
invalidJson: 0,
},
step: ((_f = (_e = ctx.state().analyze) === null || _e === void 0 ? void 0 : _e.step) !== null && _f !== void 0 ? _f : -1) + 1,
total: props.sectionWriteProgress.total,
completed: ++props.sectionWriteProgress.completed,
retry: attempt,
created_at: new Date().toISOString(),
};
ctx.dispatch(sectionEvent);
}
}
else {
throw e;
}
}
analyzeDebug(`section unit-done attempt=${attempt} fileIndex=${fileIndex} file="${state.file.filename}" moduleIndex=${moduleIndex} unitIndex=${unitIndex} sectionCount=${sectionEvent.sectionSections.length} elapsedMs=${Date.now() - sectionStart}`);
sectionsForModule.push(sectionEvent);
}
else {
// Keep existing approved section
sectionsForModule.push(state.sectionResults[moduleIndex][unitIndex]);
}
}
sectionResults.push(sectionsForModule);
}
state.sectionResults = sectionResults;
analyzeDebug(`section file-write-done attempt=${attempt} fileIndex=${fileIndex} file="${state.file.filename}"`);
// Per-module review removed — write agents self-review during rewrite loop
analyzeDebug(`section file-sections-accepted attempt=${attempt} fileIndex=${fileIndex} file="${state.file.filename}"`);
return sectionResults;
})), promptCacheKey);
// Pass 2: Cross-file lightweight review (single call)
crossFileReviewCount++;
if (crossFileReviewCount > ANALYZE_SECTION_FILE_MAX_REVIEW) {
analyzeDebug(`[orchestrateAnalyze] Section stage: skipping cross-file review (max review ${ANALYZE_SECTION_FILE_MAX_REVIEW} exceeded)`);
// Force-pass all pending files
for (const fileIndex of pendingArray)
pendingIndices.delete(fileIndex);
break;
}
analyzeDebug(`section cross-file-validation-start attempt=${attempt}`);
const filesWithSections = props.fileStates
.filter((state) => state.sectionResults !== null)
.map((state) => ({
file: state.file,
sectionEvents: state.sectionResults,
}));
// Pass 2a-pre: LLM-based key decision extraction (parallel per file)
analyzeDebug(`section decision-extraction-start attempt=${attempt}`);
const fileDecisions = yield Promise.all(filesWithSections
.filter(({ file }) => file.filename !== "00-toc.md")
.map(({ file, sectionEvents }) => (0, orchestrateAnalyzeExtractDecisions_1.orchestrateAnalyzeExtractDecisions)(ctx, {
file,
sectionEvents,
}).catch((e) => {
analyzeDebug(`section decision-extraction-error file="${file.filename}" error=${e.message}`);
return { filename: file.filename, decisions: [] };
})));
analyzeDebug(`section decision-extraction-done attempt=${attempt} files=${fileDecisions.length} totalDecisions=${fileDecisions.reduce((sum, fd) => sum + fd.decisions.length, 0)}`);
// Pass 2a-pre2: Programmatic decision conflict detection
const decisionConflicts = (0, detectDecisionConflicts_1.detectDecisionConflicts)({
fileDecisions,
});
const fileDecisionConflictMap = (0, detectDecisionConflicts_1.buildFileDecisionConflictMap)(decisionConflicts);
if (decisionConflicts.length > 0) {
analyzeDebug(`section decision-conflicts-found count=${decisionConflicts.length}: ${decisionConflicts.map((c) => `${c.topic}.${c.decision}`).join(", ")}`);
}
// Pass 2a: Programmatic cross-file validation (BEFORE LLM review)
const criticalConflicts = (0, buildConstraintConsistencyReport_1.detectConstraintConflicts)({
files: filesWithSections,
});
const fileConflictMap = (0, buildConstraintConsistencyReport_1.buildFileConflictMap)(criticalConflicts);
const attributeDuplicates = (0, buildConstraintConsistencyReport_1.detectAttributeDuplicates)({
files: filesWithSections,
});
const fileAttributeDuplicateMap = (0, buildConstraintConsistencyReport_1.buildFileAttributeDuplicateMap)(attributeDuplicates);
const enumConflicts = (0, buildConstraintConsistencyReport_1.detectEnumConflicts)({
files: filesWithSections,
});
const fileEnumConflictMap = (0, buildConstraintConsistencyReport_1.buildFileEnumConflictMap)(enumConflicts);
const permissionConflicts = (0, buildConstraintConsistencyReport_1.detectPermissionConflicts)({
files: filesWithSections,
});
const filePermissionConflictMap = (0, buildConstraintConsistencyReport_1.buildFilePermissionConflictMap)(permissionConflicts);
const stateFieldConflicts = (0, buildConstraintConsistencyReport_1.detectStateFieldConflicts)({
files: filesWithSections,
});
const fileStateFieldConflictMap = (0, buildConstraintConsistencyReport_1.buildFileStateFieldConflictMap)(stateFieldConflicts);
const errorCodeConflicts = (0, buildErrorCodeRegistry_1.detectErrorCodeConflicts)({
files: filesWithSections,
});
const fileErrorCodeConflictMap = (0, buildErrorCodeRegistry_1.buildFileErrorCodeConflictMap)(errorCodeConflicts);
const proseConflicts = (0, detectProseConstraintConflicts_1.detectProseConstraintConflicts)({
files: filesWithSections,
});
const fileProseConflictMap = (0, detectProseConstraintConflicts_1.buildFileProseConflictMap)(proseConflicts);
const oversizedTocMap = new Map();
for (const fileIndex of pendingArray) {
const state = props.fileStates[fileIndex];
if (state.file.filename === "00-toc.md" && state.sectionResults) {
const violations = (0, buildHardValidators_1.detectOversizedToc)(state.sectionResults);
if (violations.length > 0) {
oversizedTocMap.set(fileIndex, violations);
}
}
}
// Build mechanical violation summary for LLM context
const allMechanicalViolations = [
...criticalConflicts.map((c) => `Constraint conflict: ${c.key} — ${c.values.map((v) => `"${v.display}" in [${v.files.join(", ")}]`).join(" vs ")}`),
...attributeDuplicates.map((d) => `Attribute duplication: ${d.key} in [${d.files.join(", ")}]`),
...enumConflicts.map((c) => `Enum conflict: ${c.key} — ${c.values.map((v) => `enum(${v.enumSet}) in [${v.files.join(", ")}]`).join(" vs ")}`),
...errorCodeConflicts.map((c) => `Error code conflict: ${c.conditionKey} — ${c.codes.map((cd) => `HTTP ${cd.httpStatus} in [${cd.files.join(", ")}]`).join(" vs ")}`),
...proseConflicts.map((c) => `Prose constraint conflict: ${c.entityAttr} — canonical [${c.canonicalValues.join(", ")}] vs prose [${c.proseValues.join(", ")}] in ${c.file}`),
...decisionConflicts.map((c) => `Decision conflict: ${c.topic}.${c.decision} — ${c.values.map((v) => `"${v.value}" in [${v.files.join(", ")}]`).join(" vs ")}`),
];
const mechanicalViolationSummary = allMechanicalViolations.length > 0
? allMechanicalViolations.join("\n")
: undefined;
// Pass 2b: Cross-file semantic LLM review (with mechanical violations excluded)
analyzeDebug(`section cross-file-review-start attempt=${attempt}`);
let crossFileReviewEvent = null;
try {
crossFileReviewEvent = yield (0, orchestrateAnalyzeSectionCrossFileReview_1.orchestrateAnalyzeSectionCrossFileReview)(ctx, {
scenario: props.scenario,
allFileSummaries: props.fileStates
.filter((s) => s.file.filename !== "00-toc.md")
.map((state) => {
const fi = props.fileStates.indexOf(state);
return {
file: state.file,
moduleEvent: state.moduleResult,
unitEvents: state.unitResults,
sectionEvents: state.sectionResults,
status: pendingIndices.has(fi)
? attempt === 0
? "new"
: "rewritten"
: "approved",
};
}),
mechanicalViolationSummary,
fileDecisions,
progress: props.crossFileSectionReviewProgress,
promptCacheKey,
retry: attempt,
});
}
catch (e) {
if (e instanceof core_1.AgenticaValidationError ||
e instanceof AutoBeTimeoutError_1.AutoBeTimeoutError ||
e instanceof AutoBePreliminaryExhaustedError_1.AutoBePreliminaryExhaustedError) {
analyzeDebug(`section cross-file-review-force-pass attempt=${attempt} error=${e.constructor.name} — force-passing all pending files`);
for (const fileIndex of pendingArray)
pendingIndices.delete(fileIndex);
break;
}
throw e;
}
analyzeDebug(`section cross-file-review-done attempt=${attempt} results=${crossFileReviewEvent.fileResults.length}`);
// Merge results from both passes
const crossFileResultMap = new Map();
const validCrossFileResults = filterValidFileResults(crossFileReviewEvent.fileResults, props.fileStates.length, "Section cross-file review");
for (const fr of validCrossFileResults)
crossFileResultMap.set(fr.fileIndex, fr);
for (const fileIndex of pendingArray) {
const state = props.fileStates[fileIndex];
// Increment review count and force-pass if exceeded limit
state.sectionReviewCount = ((_a = state.sectionReviewCount) !== null && _a !== void 0 ? _a : 0) + 1;
if (state.sectionReviewCount > ANALYZE_SECTION_FILE_MAX_REVIEW) {
analyzeDebug(`[orchestrateAnalyze] Section stage: force-passing (max review ${ANALYZE_SECTION_FILE_MAX_REVIEW} exceeded) for file "${state.file.filename}"`);
pendingIndices.delete(fileIndex);
continue;
}
const crossFileResult = crossFileResultMap.get(fileIndex);
const perFileApproved = true; // per-file LLM review removed
const crossFileApproved = (_b = crossFileResult === null || crossFileResult === void 0 ? void 0 : crossFileResult.approved) !== null && _b !== void 0 ? _b : true;
// Check if this file has programmatically-detected critical conflicts
const filename = state.file.filename;
const fileCriticalConflicts = (_c = fileConflictMap.get(filename)) !== null && _c !== void 0 ? _c : [];
const fileAttrDuplicates = (_d = fileAttributeDuplicateMap.get(filename)) !== null && _d !== void 0 ? _d : [];
const fileEnumConflicts = (_e = fileEnumConflictMap.get(filename)) !== null && _e !== void 0 ? _e : [];
const filePermissionConflicts = (_f = filePermissionConflictMap.get(filename)) !== null && _f !== void 0 ? _f : [];
const fileStateFieldConflicts = (_g = fileStateFieldConflictMap.get(filename)) !== null && _g !== void 0 ? _g : [];
const fileErrorCodeConflicts = (_h = fileErrorCodeConflictMap.get(filename)) !== null && _h !== void 0 ? _h : [];
const fileOversizedToc = (_j = oversizedTocMap.get(fileIndex)) !== null && _j !== void 0 ? _j : [];
const fileProseConflicts = (_k = fileProseConflictMap.get(filename)) !== null && _k !== void 0 ? _k : [];
const fileDecisionConflicts = (_l = fileDecisionConflictMap.get(filename)) !== null && _l !== void 0 ? _l : [];
const hasCriticalConflict = fileCriticalConflicts.length > 0 ||
fileAttrDuplicates.length > 0 ||
fileEnumConflicts.length > 0 ||
filePermissionConflicts.length > 0 ||
fileStateFieldConflicts.length > 0 ||
fileErrorCodeConflicts.length > 0 ||
fileOversizedToc.length > 0 ||
fileProseConflicts.length > 0 ||
fileDecisionConflicts.length > 0;
// Decision logic:
// 1. per-file reject → reject (unchanged)
// 2. per-file approve + critical conflict detected → reject (NEW: patch-first)
// 3. per-file approve + no critical conflict → approve (unchanged)
const approved = perFileApproved && !hasCriticalConflict;
const structuredCrossFileIssues = collectStructuredReviewIssues(crossFileResult);
const programmaticIssues = buildProgrammaticSectionIssues({
fileCriticalConflicts,
fileAttrDuplicates,
fileEnumConflicts,
filePermissionConflicts,
fileStateFieldConflicts,
fileErrorCodeConflicts,
fileOversizedToc,
fileProseConflicts,
fileDecisionConflicts,
});
if (approved) {
// NOTE: revisedSections intentionally ignored — approved means pass as-is.
// Applying revisedSections caused infinite re-write loops (sections kept growing).
// Pass cross-file feedback as advisory for next retry's context
if (!crossFileApproved && (crossFileResult === null || crossFileResult === void 0 ? void 0 : crossFileResult.feedback)) {
state.sectionFeedback = `[Cross-file advisory] ${crossFileResult.feedback}`;
}
state.sectionRetryCount = 0;
state.sectionStagnationCount = 0;
state.lastSectionContentSignature = undefined;
state.lastSectionRejectionSignature = undefined;
pendingIndices.delete(fileIndex);
}
else {
// Critical conflict rejected (per-file approved but programmatic violations exist)
// Use cross-file rejectedModuleUnits for targeted patch if available
state.sectionFeedback = formatStructuredIssuesForRetry({
fallbackFeedback: `[Critical conflict] ${[
...fileCriticalConflicts,
...fileAttrDuplicates,
...fileEnumConflicts,
...fileProseConflicts,
...fileDecisionConflicts,
].join("; ")}` +
((crossFileResult === null || crossFileResult === void 0 ? void 0 : crossFileResult.feedback) ? `\n${crossFileResult.feedback}` : ""),
issues: [...programmaticIssues, ...structuredCrossFileIssues],
});
state.rejectedModuleUnits = normalizeRejectedModuleUnits((_m = crossFileResult === null || crossFileResult === void 0 ? void 0 : crossFileResult.rejectedModuleUnits) !== null && _m !== void 0 ? _m : null, [...programmaticIssues, ...structuredCrossFileIssues]);
// Fallback: infer targets from issues to avoid full-file rewrite
if (state.rejectedModuleUnits === null) {
state.rejectedModuleUnits = inferRejectedModuleUnitsFromIssues([...programmaticIssues, ...structuredCrossFileIssues], state.unitResults);
}
analyzeDebug(`section reject file="${state.file.filename}" attempt=${attempt} perFileApproved=${perFileApproved} crossFileApproved=${crossFileApproved} critical=${hasCriticalConflict} targets=${formatRejectedModuleUnitsSummary(state.rejectedModuleUnits)} issues=${formatReviewIssuesSummary([
...programmaticIssues,
...structuredCrossFileIssues,
])} feedback=${truncateForDebug((_o = state.sectionFeedback) !== null && _o !== void 0 ? _o : "", 500)}`);
}
if (!approved) {
const contentSignature = buildSectionContentSignature(state);
const rejectionSignature = buildSectionRejectionSignature({
rejectedModuleUnits: (_p = state.rejectedModuleUnits) !== null && _p !== void 0 ? _p : null,
feedback: (_q = state.sectionFeedback) !== null && _q !== void 0 ? _q : "",
});
const isStagnant = state.lastSectionContentSignature === contentSignature &&
state.lastSectionRejectionSignature === rejectionSignature;
state.sectionStagnationCount = isStagnant
? ((_r = state.sectionStagnationCount) !== null && _r !== void 0 ? _r : 0) + 1
: 0;
state.sectionRetryCount = ((_s = state.sectionRetryCount) !== null && _s !== void 0 ? _s : 0) + 1;
state.lastSectionContentSignature = contentSignature;
state.lastSectionRejectionSignature = rejectionSignature;
if (((_t = state.sectionRetryCount) !== null && _t !== void 0 ? _t : 0) > ANALYZE_SECTION_FILE_MAX_RETRY) {
analyzeDebug(`[orchestrateAnalyze] Section stage: force-passing (max retry exceeded: ${ANALYZE_SECTION_FILE_MAX_RETRY}) for file "${state.file.filename}"`);
pendingIndices.delete(fileIndex);
continue;
}
if (((_u = state.sectionStagnationCount) !== null && _u !== void 0 ? _u : 0) >= ANALYZE_SECTION_STAGNATION_MAX) {
analyzeDebug(`[orchestrateAnalyze] Section stage: force-passing (stagnation detected ${state.sectionStagnationCount}x) for file "${state.file.filename}"`);
pendingIndices.delete(fileIndex);
continue;
}
}
}
}
if (pendingIndices.size > 0) {
analyzeDebug(`[orchestrateAnalyze] Section stage: force-passing after max retries for files: ${[
...pendingIndices,
]
.map((i) => props.fileStates[i].file.filename)
.join(", ")}`);
}
});
}
// ─── Section-stage helper functions ───
function computeSectionBatchSize(props) {
return Math.min(8, props.pendingCount);
}
function chunkSectionFileIndices(indices, size) {
if (indices.length === 0)
return [];
if (size <= 0 || size >= indices.length)
return [indices];
const chunks = [];
for (let i = 0; i < indices.length; i += size)
chunks.push(indices.slice(i, i + size));
return chunks;
}
function buildRejectedSet(rejected) {
if (rejected == null)
return null;
if (rejected.length === 0)
return null;
const set = new Set();
for (const entry of rejected) {
for (const ui of entry.unitIndices) {
set.add(`${entry.moduleIndex}:${ui}`);
}
}
return set.size > 0 ? set : null;
}
function buildFeedbackMap(rejected) {
var _a, _b;
const map = new Map();
if (rejected == null)
return map;
for (const entry of rejected) {
for (const ui of entry.unitIndices) {
map.set(`${entry.moduleIndex}:${ui}`, {
feedback: formatRejectedModuleUnitFeedback(entry, ui),
sectionIndices: (_b = (_a = entry.sectionIndicesPerUnit) === null || _a === void 0 ? void 0 : _a[ui]) !== null && _b !== void 0 ? _b : null,
});
}
}
return map;
}
function isSectionRejected(rejectedSet, moduleIndex, unitIndex) {
if (rejectedSet === null)
return true;
return rejectedSet.has(`${moduleIndex}:${unitIndex}`);
}
function filterValidFileResults(fileResults, fileCount, stage) {
return fileResults.filter((fr) => {
if (Number.isInteger(fr.fileIndex) &&
fr.fileIndex >= 0 &&
fr.fileIndex < fileCount) {
return true;
}
console.warn(`[orchestrateAnalyze] ${stage}: invalid fileIndex ${fr.fileIndex} (valid: 0-${fileCount - 1})`);
return false;
});
}
function formatRejectedModuleUnitFeedback(entry, unitIndex) {
var _a;
const scopedIssues = ((_a = entry.issues) !== null && _a !== void 0 ? _a : []).filter((issue) => issue.moduleIndex === entry.moduleIndex &&
(issue.unitIndex === null || issue.unitIndex === unitIndex));
if (scopedIssues.length === 0)
return entry.feedback;
return [
entry.feedback,
...scopedIssues.map((issue) => `- [${issue.ruleCode}] target=${formatIssueTarget(issue)} fix=${issue.fixInstruction}`),
].join("\n");
}
function collectStructuredReviewIssues(result) {
var _a, _b, _c, _d, _e;
if (!result)
return [];
const collected = [];
for (const issue of (_a = result.issues) !== null && _a !== void 0 ? _a : [])
collected.push(issue);
for (const group of (_b = result.rejectedModuleUnits) !== null && _b !== void 0 ? _b : []) {
for (const issue of (_c = group.issues) !== null && _c !== void 0 ? _c : [])
collected.push(issue);
if (((_e = (_d = group.issues) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0) === 0) {
for (const unitIndex of group.unitIndices) {
collected.push({
ruleCode: "section_review_reject",
moduleIndex: group.moduleIndex,
unitIndex,
fixInstruction: group.feedback || result.feedback || "Fix review issues.",
evidence: null,
});
}
}
}
if (collected.length === 0 && result.feedback.trim().length > 0) {
collected.push({
ruleCode: "section_review_reject",
moduleIndex: null,
unitIndex: null,
fixInstruction: result.feedback,
evidence: null,
});
}
return dedupeReviewIssues(collected);
}
function buildProgrammaticSectionIssues(props) {
return [
...props.fileCriticalConflicts.map((detail) => ({
ruleCode: "cross_file_constraint_conflict",
moduleIndex: null,
unitIndex: null,
fixInstruction: "Align conflicting constraints/values with other files and preserve one canonical value.",