UNPKG

@autobe/agent

Version:

AI backend server code generator

871 lines (870 loc) 60.8 kB
"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.",