@autobe/agent 0.30.2 → 0.30.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/AutoBeMockAgent.js +0 -2
- package/lib/AutoBeMockAgent.js.map +1 -1
- package/lib/constants/AutoBeConfigConstant.d.ts +1 -1
- package/lib/constants/AutoBeSystemPromptConstant.d.ts +12 -11
- package/lib/constants/AutoBeSystemPromptConstant.js.map +1 -1
- package/lib/index.mjs +498 -55
- package/lib/index.mjs.map +1 -1
- package/lib/orchestrate/analyze/histories/transformAnalyzeExtractDecisionsHistory.d.ts +18 -0
- package/lib/orchestrate/analyze/histories/transformAnalyzeExtractDecisionsHistory.js +51 -0
- package/lib/orchestrate/analyze/histories/transformAnalyzeExtractDecisionsHistory.js.map +1 -0
- package/lib/orchestrate/analyze/histories/transformAnalyzeScenarioHistory.js +1 -1
- package/lib/orchestrate/analyze/histories/transformAnalyzeScenarioHistory.js.map +1 -1
- package/lib/orchestrate/analyze/histories/transformAnalyzeScenarioReviewHistory.js +1 -1
- package/lib/orchestrate/analyze/histories/transformAnalyzeScenarioReviewHistory.js.map +1 -1
- package/lib/orchestrate/analyze/histories/transformAnalyzeSectionCrossFileReviewHistory.d.ts +1 -0
- package/lib/orchestrate/analyze/histories/transformAnalyzeSectionCrossFileReviewHistory.js +15 -1
- package/lib/orchestrate/analyze/histories/transformAnalyzeSectionCrossFileReviewHistory.js.map +1 -1
- package/lib/orchestrate/analyze/histories/transformAnalyzeSectionReviewHistory.js +1 -1
- package/lib/orchestrate/analyze/histories/transformAnalyzeSectionReviewHistory.js.map +1 -1
- package/lib/orchestrate/analyze/orchestrateAnalyze.js +48 -13
- package/lib/orchestrate/analyze/orchestrateAnalyze.js.map +1 -1
- package/lib/orchestrate/analyze/orchestrateAnalyzeExtractDecisions.d.ts +17 -0
- package/lib/orchestrate/analyze/orchestrateAnalyzeExtractDecisions.js +345 -0
- package/lib/orchestrate/analyze/orchestrateAnalyzeExtractDecisions.js.map +1 -0
- package/lib/orchestrate/analyze/orchestrateAnalyzeScenario.js +2 -2
- package/lib/orchestrate/analyze/orchestrateAnalyzeSectionCrossFileReview.d.ts +1 -0
- package/lib/orchestrate/analyze/orchestrateAnalyzeSectionCrossFileReview.js +1 -0
- package/lib/orchestrate/analyze/orchestrateAnalyzeSectionCrossFileReview.js.map +1 -1
- package/lib/orchestrate/analyze/structures/IAutoBeAnalyzeExtractDecisionsApplication.d.ts +91 -0
- package/lib/orchestrate/analyze/structures/IAutoBeAnalyzeExtractDecisionsApplication.js +3 -0
- package/lib/orchestrate/analyze/structures/IAutoBeAnalyzeExtractDecisionsApplication.js.map +1 -0
- package/lib/orchestrate/analyze/structures/IAutoBeAnalyzeScenarioApplication.d.ts +15 -5
- package/lib/orchestrate/analyze/utils/buildErrorCodeRegistry.d.ts +0 -9
- package/lib/orchestrate/analyze/utils/buildErrorCodeRegistry.js +1 -13
- package/lib/orchestrate/analyze/utils/buildErrorCodeRegistry.js.map +1 -1
- package/lib/orchestrate/analyze/utils/detectDecisionConflicts.d.ts +63 -0
- package/lib/orchestrate/analyze/utils/detectDecisionConflicts.js +105 -0
- package/lib/orchestrate/analyze/utils/detectDecisionConflicts.js.map +1 -0
- package/lib/orchestrate/common/histories/transformPreliminaryHistory.js +1 -1
- package/lib/orchestrate/common/histories/transformPreliminaryHistory.js.map +1 -1
- package/lib/orchestrate/interface/histories/transformInterfaceActionEndpointReviewHistory.js +1 -1
- package/lib/orchestrate/interface/histories/transformInterfaceActionEndpointReviewHistory.js.map +1 -1
- package/lib/orchestrate/interface/histories/transformInterfaceBaseEndpointReviewHistory.js +1 -1
- package/lib/orchestrate/interface/histories/transformInterfaceBaseEndpointReviewHistory.js.map +1 -1
- package/lib/orchestrate/interface/histories/transformInterfaceSchemaRefineHistory.js +1 -1
- package/lib/orchestrate/interface/histories/transformInterfaceSchemaRefineHistory.js.map +1 -1
- package/lib/orchestrate/interface/orchestrateInterfaceSchemaRefine.js +1 -2
- package/lib/orchestrate/interface/orchestrateInterfaceSchemaRefine.js.map +1 -1
- package/lib/orchestrate/interface/orchestrateInterfaceSchemaReview.js +1 -2
- package/lib/orchestrate/interface/orchestrateInterfaceSchemaReview.js.map +1 -1
- package/lib/orchestrate/interface/utils/AutoBeJsonSchemaValidator.js +36 -0
- package/lib/orchestrate/interface/utils/AutoBeJsonSchemaValidator.js.map +1 -1
- package/lib/orchestrate/prisma/histories/transformPrismaComponentReviewHistory.js +2 -2
- package/lib/orchestrate/prisma/histories/transformPrismaComponentReviewHistory.js.map +1 -1
- package/lib/orchestrate/prisma/histories/transformPrismaComponentsHistory.js +1 -1
- package/lib/orchestrate/prisma/histories/transformPrismaComponentsHistory.js.map +1 -1
- package/lib/orchestrate/prisma/histories/transformPrismaGroupHistory.js +1 -1
- package/lib/orchestrate/prisma/histories/transformPrismaGroupHistory.js.map +1 -1
- package/lib/orchestrate/prisma/histories/transformPrismaGroupReviewHistory.js +1 -1
- package/lib/orchestrate/prisma/histories/transformPrismaGroupReviewHistory.js.map +1 -1
- package/lib/orchestrate/prisma/histories/transformPrismaSchemaHistory.js +1 -1
- package/lib/orchestrate/prisma/histories/transformPrismaSchemaHistory.js.map +1 -1
- package/lib/orchestrate/prisma/histories/transformPrismaSchemaReviewHistory.js +1 -1
- package/lib/orchestrate/prisma/histories/transformPrismaSchemaReviewHistory.js.map +1 -1
- package/lib/orchestrate/prisma/orchestratePrismaCorrect.js +1 -1
- package/package.json +5 -5
- package/src/AutoBeMockAgent.ts +0 -2
- package/src/constants/AutoBeConfigConstant.ts +1 -1
- package/src/constants/AutoBeSystemPromptConstant.ts +12 -11
- package/src/orchestrate/analyze/histories/transformAnalyzeExtractDecisionsHistory.ts +69 -0
- package/src/orchestrate/analyze/histories/transformAnalyzeSectionCrossFileReviewHistory.ts +20 -0
- package/src/orchestrate/analyze/orchestrateAnalyze.ts +58 -1
- package/src/orchestrate/analyze/orchestrateAnalyzeExtractDecisions.ts +97 -0
- package/src/orchestrate/analyze/orchestrateAnalyzeSectionCrossFileReview.ts +2 -0
- package/src/orchestrate/analyze/structures/IAutoBeAnalyzeExtractDecisionsApplication.ts +99 -0
- package/src/orchestrate/analyze/structures/IAutoBeAnalyzeScenarioApplication.ts +15 -5
- package/src/orchestrate/analyze/utils/buildErrorCodeRegistry.ts +0 -20
- package/src/orchestrate/analyze/utils/detectDecisionConflicts.ts +172 -0
- package/src/orchestrate/interface/orchestrateInterfaceSchemaRefine.ts +1 -2
- package/src/orchestrate/interface/orchestrateInterfaceSchemaReview.ts +1 -2
- package/src/orchestrate/interface/utils/AutoBeJsonSchemaValidator.ts +38 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AutoBeAnalyze,
|
|
3
|
+
AutoBeAnalyzeWriteSectionEvent,
|
|
4
|
+
} from "@autobe/interface";
|
|
5
|
+
import { StringUtil } from "@autobe/utils";
|
|
6
|
+
import { v7 } from "uuid";
|
|
7
|
+
|
|
8
|
+
import { AutoBeSystemPromptConstant } from "../../../constants/AutoBeSystemPromptConstant";
|
|
9
|
+
import { AutoBeContext } from "../../../context/AutoBeContext";
|
|
10
|
+
import { IAutoBeOrchestrateHistory } from "../../../structures/IAutoBeOrchestrateHistory";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Transform histories for key decision extraction from a single file.
|
|
14
|
+
*
|
|
15
|
+
* Provides the extractor with:
|
|
16
|
+
*
|
|
17
|
+
* 1. The system prompt for decision extraction
|
|
18
|
+
* 2. The full section content of ONE file
|
|
19
|
+
*
|
|
20
|
+
* Each file is processed independently in parallel, so only one file's content
|
|
21
|
+
* is included per call.
|
|
22
|
+
*/
|
|
23
|
+
export const transformAnalyzeExtractDecisionsHistory = (
|
|
24
|
+
_ctx: AutoBeContext,
|
|
25
|
+
props: {
|
|
26
|
+
file: AutoBeAnalyze.IFileScenario;
|
|
27
|
+
sectionEvents: AutoBeAnalyzeWriteSectionEvent[][];
|
|
28
|
+
},
|
|
29
|
+
): IAutoBeOrchestrateHistory => {
|
|
30
|
+
// Build full section content for this file
|
|
31
|
+
const fileContent = props.sectionEvents
|
|
32
|
+
.map((sectionsForModule, moduleIndex) =>
|
|
33
|
+
sectionsForModule
|
|
34
|
+
.map((sectionEvent, unitIndex) =>
|
|
35
|
+
sectionEvent.sectionSections
|
|
36
|
+
.map(
|
|
37
|
+
(section) =>
|
|
38
|
+
`### [M${moduleIndex + 1}.U${unitIndex + 1}] ${section.title}\n\n${section.content}`,
|
|
39
|
+
)
|
|
40
|
+
.join("\n\n"),
|
|
41
|
+
)
|
|
42
|
+
.join("\n\n"),
|
|
43
|
+
)
|
|
44
|
+
.join("\n\n---\n\n");
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
histories: [
|
|
48
|
+
{
|
|
49
|
+
id: v7(),
|
|
50
|
+
created_at: new Date().toISOString(),
|
|
51
|
+
type: "systemMessage",
|
|
52
|
+
text: AutoBeSystemPromptConstant.ANALYZE_EXTRACT_DECISIONS,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: v7(),
|
|
56
|
+
created_at: new Date().toISOString(),
|
|
57
|
+
type: "assistantMessage",
|
|
58
|
+
text: StringUtil.trim`
|
|
59
|
+
## File: ${props.file.filename}
|
|
60
|
+
|
|
61
|
+
## Full Section Content
|
|
62
|
+
|
|
63
|
+
${fileContent}
|
|
64
|
+
`,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
userMessage: `Extract all binary and discrete decisions from the file "${props.file.filename}". Return structured decisions for cross-file contradiction detection.`,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
@@ -33,6 +33,7 @@ export const transformAnalyzeSectionCrossFileReviewHistory = (
|
|
|
33
33
|
status: "approved" | "rewritten" | "new";
|
|
34
34
|
}>;
|
|
35
35
|
mechanicalViolationSummary?: string;
|
|
36
|
+
fileDecisions?: import("../utils/detectDecisionConflicts").IFileDecisions[];
|
|
36
37
|
preliminary: null | AutoBePreliminaryController<"previousAnalysisSections">;
|
|
37
38
|
},
|
|
38
39
|
): IAutoBeOrchestrateHistory => {
|
|
@@ -115,6 +116,25 @@ export const transformAnalyzeSectionCrossFileReviewHistory = (
|
|
|
115
116
|
: ""
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
${
|
|
120
|
+
props.fileDecisions && props.fileDecisions.length > 0
|
|
121
|
+
? `
|
|
122
|
+
## Extracted Key Decisions Per File
|
|
123
|
+
|
|
124
|
+
Below are the key behavioral decisions extracted from each file.
|
|
125
|
+
Use these to verify cross-file consistency — same topic+decision should have the same value across files.
|
|
126
|
+
|
|
127
|
+
${props.fileDecisions
|
|
128
|
+
.filter((fd) => fd.decisions.length > 0)
|
|
129
|
+
.map(
|
|
130
|
+
(fd) =>
|
|
131
|
+
`**${fd.filename}**:\n${fd.decisions.map((d) => `- ${d.topic}.${d.decision} = "${d.value}" — ${d.evidence.slice(0, 100)}`).join("\n")}`,
|
|
132
|
+
)
|
|
133
|
+
.join("\n\n")}
|
|
134
|
+
`
|
|
135
|
+
: ""
|
|
136
|
+
}
|
|
137
|
+
|
|
118
138
|
## Cross-File Semantic Consistency Criteria
|
|
119
139
|
|
|
120
140
|
Focus ONLY on issues requiring human-like judgment:
|
|
@@ -22,6 +22,7 @@ import { AutoBePreliminaryExhaustedError } from "../../utils/AutoBePreliminaryEx
|
|
|
22
22
|
import { AutoBeTimeoutError } from "../../utils/AutoBeTimeoutError";
|
|
23
23
|
import { executeCachedBatch } from "../../utils/executeCachedBatch";
|
|
24
24
|
import { fillTocDeterministic } from "./fillTocDeterministic";
|
|
25
|
+
import { orchestrateAnalyzeExtractDecisions } from "./orchestrateAnalyzeExtractDecisions";
|
|
25
26
|
import { orchestrateAnalyzeScenario } from "./orchestrateAnalyzeScenario";
|
|
26
27
|
import { orchestrateAnalyzeScenarioReview } from "./orchestrateAnalyzeScenarioReview";
|
|
27
28
|
import { orchestrateAnalyzeSectionCrossFileReview } from "./orchestrateAnalyzeSectionCrossFileReview";
|
|
@@ -56,6 +57,11 @@ import {
|
|
|
56
57
|
detectErrorCodeConflicts,
|
|
57
58
|
} from "./utils/buildErrorCodeRegistry";
|
|
58
59
|
import { detectOversizedToc } from "./utils/buildHardValidators";
|
|
60
|
+
import {
|
|
61
|
+
IFileDecisions,
|
|
62
|
+
buildFileDecisionConflictMap,
|
|
63
|
+
detectDecisionConflicts,
|
|
64
|
+
} from "./utils/detectDecisionConflicts";
|
|
59
65
|
import {
|
|
60
66
|
buildFileProseConflictMap,
|
|
61
67
|
detectProseConstraintConflicts,
|
|
@@ -836,6 +842,39 @@ async function processStageSection(
|
|
|
836
842
|
sectionEvents: state.sectionResults!,
|
|
837
843
|
}));
|
|
838
844
|
|
|
845
|
+
// Pass 2a-pre: LLM-based key decision extraction (parallel per file)
|
|
846
|
+
analyzeDebug(`section decision-extraction-start attempt=${attempt}`);
|
|
847
|
+
const fileDecisions: IFileDecisions[] = await Promise.all(
|
|
848
|
+
filesWithSections
|
|
849
|
+
.filter(({ file }) => file.filename !== "00-toc.md")
|
|
850
|
+
.map(({ file, sectionEvents }) =>
|
|
851
|
+
orchestrateAnalyzeExtractDecisions(ctx, {
|
|
852
|
+
file,
|
|
853
|
+
sectionEvents,
|
|
854
|
+
}).catch((e) => {
|
|
855
|
+
analyzeDebug(
|
|
856
|
+
`section decision-extraction-error file="${file.filename}" error=${(e as Error).message}`,
|
|
857
|
+
);
|
|
858
|
+
return { filename: file.filename, decisions: [] } as IFileDecisions;
|
|
859
|
+
}),
|
|
860
|
+
),
|
|
861
|
+
);
|
|
862
|
+
analyzeDebug(
|
|
863
|
+
`section decision-extraction-done attempt=${attempt} files=${fileDecisions.length} totalDecisions=${fileDecisions.reduce((sum, fd) => sum + fd.decisions.length, 0)}`,
|
|
864
|
+
);
|
|
865
|
+
|
|
866
|
+
// Pass 2a-pre2: Programmatic decision conflict detection
|
|
867
|
+
const decisionConflicts = detectDecisionConflicts({
|
|
868
|
+
fileDecisions,
|
|
869
|
+
});
|
|
870
|
+
const fileDecisionConflictMap: Map<string, string[]> =
|
|
871
|
+
buildFileDecisionConflictMap(decisionConflicts);
|
|
872
|
+
if (decisionConflicts.length > 0) {
|
|
873
|
+
analyzeDebug(
|
|
874
|
+
`section decision-conflicts-found count=${decisionConflicts.length}: ${decisionConflicts.map((c) => `${c.topic}.${c.decision}`).join(", ")}`,
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
|
|
839
878
|
// Pass 2a: Programmatic cross-file validation (BEFORE LLM review)
|
|
840
879
|
const criticalConflicts = detectConstraintConflicts({
|
|
841
880
|
files: filesWithSections,
|
|
@@ -911,6 +950,10 @@ async function processStageSection(
|
|
|
911
950
|
(c) =>
|
|
912
951
|
`Prose constraint conflict: ${c.entityAttr} — canonical [${c.canonicalValues.join(", ")}] vs prose [${c.proseValues.join(", ")}] in ${c.file}`,
|
|
913
952
|
),
|
|
953
|
+
...decisionConflicts.map(
|
|
954
|
+
(c) =>
|
|
955
|
+
`Decision conflict: ${c.topic}.${c.decision} — ${c.values.map((v) => `"${v.value}" in [${v.files.join(", ")}]`).join(" vs ")}`,
|
|
956
|
+
),
|
|
914
957
|
];
|
|
915
958
|
const mechanicalViolationSummary =
|
|
916
959
|
allMechanicalViolations.length > 0
|
|
@@ -942,6 +985,7 @@ async function processStageSection(
|
|
|
942
985
|
};
|
|
943
986
|
}),
|
|
944
987
|
mechanicalViolationSummary,
|
|
988
|
+
fileDecisions,
|
|
945
989
|
progress: props.crossFileSectionReviewProgress,
|
|
946
990
|
promptCacheKey,
|
|
947
991
|
retry: attempt,
|
|
@@ -1011,6 +1055,7 @@ async function processStageSection(
|
|
|
1011
1055
|
fileErrorCodeConflictMap.get(filename) ?? [];
|
|
1012
1056
|
const fileOversizedToc = oversizedTocMap.get(fileIndex) ?? [];
|
|
1013
1057
|
const fileProseConflicts = fileProseConflictMap.get(filename) ?? [];
|
|
1058
|
+
const fileDecisionConflicts = fileDecisionConflictMap.get(filename) ?? [];
|
|
1014
1059
|
const hasCriticalConflict =
|
|
1015
1060
|
fileCriticalConflicts.length > 0 ||
|
|
1016
1061
|
fileAttrDuplicates.length > 0 ||
|
|
@@ -1019,7 +1064,8 @@ async function processStageSection(
|
|
|
1019
1064
|
fileStateFieldConflicts.length > 0 ||
|
|
1020
1065
|
fileErrorCodeConflicts.length > 0 ||
|
|
1021
1066
|
fileOversizedToc.length > 0 ||
|
|
1022
|
-
fileProseConflicts.length > 0
|
|
1067
|
+
fileProseConflicts.length > 0 ||
|
|
1068
|
+
fileDecisionConflicts.length > 0;
|
|
1023
1069
|
|
|
1024
1070
|
// Decision logic:
|
|
1025
1071
|
// 1. per-file reject → reject (unchanged)
|
|
@@ -1040,6 +1086,7 @@ async function processStageSection(
|
|
|
1040
1086
|
fileErrorCodeConflicts,
|
|
1041
1087
|
fileOversizedToc,
|
|
1042
1088
|
fileProseConflicts,
|
|
1089
|
+
fileDecisionConflicts,
|
|
1043
1090
|
});
|
|
1044
1091
|
|
|
1045
1092
|
if (approved) {
|
|
@@ -1091,6 +1138,7 @@ async function processStageSection(
|
|
|
1091
1138
|
...fileAttrDuplicates,
|
|
1092
1139
|
...fileEnumConflicts,
|
|
1093
1140
|
...fileProseConflicts,
|
|
1141
|
+
...fileDecisionConflicts,
|
|
1094
1142
|
].join("; ")}` +
|
|
1095
1143
|
(crossFileResult?.feedback ? `\n${crossFileResult.feedback}` : ""),
|
|
1096
1144
|
issues: [...programmaticIssues, ...structuredCrossFileIssues],
|
|
@@ -1316,6 +1364,7 @@ function buildProgrammaticSectionIssues(props: {
|
|
|
1316
1364
|
fileErrorCodeConflicts: string[];
|
|
1317
1365
|
fileOversizedToc: string[];
|
|
1318
1366
|
fileProseConflicts: string[];
|
|
1367
|
+
fileDecisionConflicts: string[];
|
|
1319
1368
|
}): AutoBeAnalyzeSectionReviewIssue[] {
|
|
1320
1369
|
return [
|
|
1321
1370
|
...props.fileCriticalConflicts.map((detail) => ({
|
|
@@ -1382,6 +1431,14 @@ function buildProgrammaticSectionIssues(props: {
|
|
|
1382
1431
|
"Remove the restated constraint value and use a backtick reference to the canonical definition in 02-domain-model instead. Example: 'THE system SHALL validate `User.bio` per entity constraints (see 02-domain-model)'",
|
|
1383
1432
|
evidence: detail,
|
|
1384
1433
|
})),
|
|
1434
|
+
...props.fileDecisionConflicts.map((detail) => ({
|
|
1435
|
+
ruleCode: "cross_file_decision_conflict",
|
|
1436
|
+
moduleIndex: null,
|
|
1437
|
+
unitIndex: null,
|
|
1438
|
+
fixInstruction:
|
|
1439
|
+
"This file contradicts another file on a key behavioral decision. Align with the canonical source file for this topic.",
|
|
1440
|
+
evidence: detail,
|
|
1441
|
+
})),
|
|
1385
1442
|
];
|
|
1386
1443
|
}
|
|
1387
1444
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { IAgenticaController } from "@agentica/core";
|
|
2
|
+
import {
|
|
3
|
+
AutoBeAnalyze,
|
|
4
|
+
AutoBeAnalyzeWriteSectionEvent,
|
|
5
|
+
AutoBeEventSource,
|
|
6
|
+
} from "@autobe/interface";
|
|
7
|
+
import { IPointer } from "tstl";
|
|
8
|
+
import typia, { ILlmApplication, IValidation } from "typia";
|
|
9
|
+
|
|
10
|
+
import { AutoBeContext } from "../../context/AutoBeContext";
|
|
11
|
+
import { transformAnalyzeExtractDecisionsHistory } from "./histories/transformAnalyzeExtractDecisionsHistory";
|
|
12
|
+
import {
|
|
13
|
+
IAutoBeAnalyzeExtractDecisionsApplication,
|
|
14
|
+
IAutoBeAnalyzeExtractDecisionsApplicationComplete,
|
|
15
|
+
IAutoBeAnalyzeExtractDecisionsApplicationProps,
|
|
16
|
+
} from "./structures/IAutoBeAnalyzeExtractDecisionsApplication";
|
|
17
|
+
import { IFileDecisions } from "./utils/detectDecisionConflicts";
|
|
18
|
+
import { isRecord, tryParseStringAsRecord } from "./utils/repairUtils";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extract key decisions from a single file's section content.
|
|
22
|
+
*
|
|
23
|
+
* This orchestrator calls the LLM to read one file's full section content and
|
|
24
|
+
* extract binary/discrete decisions as structured data. The extracted decisions
|
|
25
|
+
* are then used for programmatic cross-file contradiction detection.
|
|
26
|
+
*
|
|
27
|
+
* Called in parallel for each file (excluding 00-toc.md) after per-file review
|
|
28
|
+
* approves.
|
|
29
|
+
*/
|
|
30
|
+
export const orchestrateAnalyzeExtractDecisions = async (
|
|
31
|
+
ctx: AutoBeContext,
|
|
32
|
+
props: {
|
|
33
|
+
file: AutoBeAnalyze.IFileScenario;
|
|
34
|
+
sectionEvents: AutoBeAnalyzeWriteSectionEvent[][];
|
|
35
|
+
},
|
|
36
|
+
): Promise<IFileDecisions> => {
|
|
37
|
+
const pointer: IPointer<IAutoBeAnalyzeExtractDecisionsApplicationComplete | null> =
|
|
38
|
+
{
|
|
39
|
+
value: null,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
await ctx.conversate({
|
|
43
|
+
source: SOURCE,
|
|
44
|
+
controller: createController({ pointer }),
|
|
45
|
+
enforceFunctionCall: true,
|
|
46
|
+
...transformAnalyzeExtractDecisionsHistory(ctx, {
|
|
47
|
+
file: props.file,
|
|
48
|
+
sectionEvents: props.sectionEvents,
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
filename: props.file.filename,
|
|
54
|
+
decisions: (pointer.value?.decisions ?? []).map((d) => ({
|
|
55
|
+
topic: d.topic,
|
|
56
|
+
decision: d.decision,
|
|
57
|
+
value: d.value,
|
|
58
|
+
evidence: d.evidence,
|
|
59
|
+
})),
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function createController(props: {
|
|
64
|
+
pointer: IPointer<IAutoBeAnalyzeExtractDecisionsApplicationComplete | null>;
|
|
65
|
+
}): IAgenticaController.IClass {
|
|
66
|
+
const application: ILlmApplication =
|
|
67
|
+
typia.llm.application<IAutoBeAnalyzeExtractDecisionsApplication>({
|
|
68
|
+
validate: {
|
|
69
|
+
process: (
|
|
70
|
+
input: unknown,
|
|
71
|
+
): IValidation<IAutoBeAnalyzeExtractDecisionsApplicationProps> => {
|
|
72
|
+
if (isRecord(input) && typeof input.request === "string") {
|
|
73
|
+
input = {
|
|
74
|
+
...input,
|
|
75
|
+
request: tryParseStringAsRecord(input.request),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return typia.validate<IAutoBeAnalyzeExtractDecisionsApplicationProps>(
|
|
79
|
+
input,
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
protocol: "class",
|
|
86
|
+
name: SOURCE,
|
|
87
|
+
application,
|
|
88
|
+
execute: {
|
|
89
|
+
process: (input) => {
|
|
90
|
+
if (input.request.type === "complete")
|
|
91
|
+
props.pointer.value = input.request;
|
|
92
|
+
},
|
|
93
|
+
} satisfies IAutoBeAnalyzeExtractDecisionsApplication,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const SOURCE = "analyzeSectionReview" satisfies AutoBeEventSource;
|
|
@@ -47,6 +47,7 @@ export const orchestrateAnalyzeSectionCrossFileReview = async (
|
|
|
47
47
|
status: "approved" | "rewritten" | "new";
|
|
48
48
|
}>;
|
|
49
49
|
mechanicalViolationSummary?: string;
|
|
50
|
+
fileDecisions?: import("./utils/detectDecisionConflicts").IFileDecisions[];
|
|
50
51
|
progress: AutoBeProgressEventBase;
|
|
51
52
|
promptCacheKey: string;
|
|
52
53
|
retry: number;
|
|
@@ -77,6 +78,7 @@ export const orchestrateAnalyzeSectionCrossFileReview = async (
|
|
|
77
78
|
scenario: props.scenario,
|
|
78
79
|
allFileSummaries: props.allFileSummaries,
|
|
79
80
|
mechanicalViolationSummary: props.mechanicalViolationSummary,
|
|
81
|
+
fileDecisions: props.fileDecisions,
|
|
80
82
|
preliminary,
|
|
81
83
|
}),
|
|
82
84
|
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application interface for the Key Decision Extractor agent.
|
|
3
|
+
*
|
|
4
|
+
* This agent extracts binary/discrete decisions from a single file's section
|
|
5
|
+
* content as structured data. The extracted decisions are then compared
|
|
6
|
+
* programmatically across files to detect contradictions.
|
|
7
|
+
*/
|
|
8
|
+
export interface IAutoBeAnalyzeExtractDecisionsApplication {
|
|
9
|
+
/**
|
|
10
|
+
* Process decision extraction from a single file's sections.
|
|
11
|
+
*
|
|
12
|
+
* Reads the file content and extracts all binary/discrete decisions as
|
|
13
|
+
* structured data for cross-file contradiction detection.
|
|
14
|
+
*
|
|
15
|
+
* @param props Request containing extracted decisions
|
|
16
|
+
*/
|
|
17
|
+
process(props: IAutoBeAnalyzeExtractDecisionsApplicationProps): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface IAutoBeAnalyzeExtractDecisionsApplicationProps {
|
|
21
|
+
/**
|
|
22
|
+
* Think before you act.
|
|
23
|
+
*
|
|
24
|
+
* Before completing extraction, reflect on what decisions this file makes:
|
|
25
|
+
*
|
|
26
|
+
* - What binary (yes/no) choices does this file assert?
|
|
27
|
+
* - What discrete behavioral choices does this file make?
|
|
28
|
+
* - Are there decisions where another file could reasonably disagree?
|
|
29
|
+
*/
|
|
30
|
+
thinking?: string | null;
|
|
31
|
+
|
|
32
|
+
/** Extraction result. */
|
|
33
|
+
request: IAutoBeAnalyzeExtractDecisionsApplicationComplete;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Request to complete the decision extraction. */
|
|
37
|
+
export interface IAutoBeAnalyzeExtractDecisionsApplicationComplete {
|
|
38
|
+
/** Type discriminator for the request. */
|
|
39
|
+
type: "complete";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* All binary/discrete decisions extracted from this file.
|
|
43
|
+
*
|
|
44
|
+
* Each decision represents a specific behavioral choice that the file makes.
|
|
45
|
+
* Use normalized topic names, decision names, and values.
|
|
46
|
+
*
|
|
47
|
+
* Return an empty array if the file has no extractable decisions (e.g., table
|
|
48
|
+
* of contents).
|
|
49
|
+
*/
|
|
50
|
+
decisions: IAutoBeAnalyzeExtractedDecision[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A single extracted decision from the file's content. */
|
|
54
|
+
export interface IAutoBeAnalyzeExtractedDecision {
|
|
55
|
+
/**
|
|
56
|
+
* Normalized topic grouping.
|
|
57
|
+
*
|
|
58
|
+
* Use lowercase, underscore-separated names.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* (password_change, "todo_deletion", "edit_history");
|
|
62
|
+
*/
|
|
63
|
+
topic: string;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Specific decision within the topic.
|
|
67
|
+
*
|
|
68
|
+
* Use lowercase, underscore-separated, descriptive names.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* (requires_current_password, "deletion_method", "recorded_values");
|
|
72
|
+
*/
|
|
73
|
+
decision: string;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The value of the decision.
|
|
77
|
+
*
|
|
78
|
+
* For binary decisions: "yes" or "no". For discrete decisions: short
|
|
79
|
+
* descriptive string.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* (yes,
|
|
83
|
+
* "no",
|
|
84
|
+
* "soft_delete",
|
|
85
|
+
* "hard_delete",
|
|
86
|
+
* "new_values",
|
|
87
|
+
* "previous_values");
|
|
88
|
+
*/
|
|
89
|
+
value: string;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Short quote (1-2 sentences) from the source text supporting this decision.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* "A user may change their password only by providing their
|
|
96
|
+
* current password."
|
|
97
|
+
*/
|
|
98
|
+
evidence: string;
|
|
99
|
+
}
|
|
@@ -129,12 +129,22 @@ export namespace IAutoBeAnalyzeScenarioApplication {
|
|
|
129
129
|
/**
|
|
130
130
|
* High-level project features that activate conditional modules.
|
|
131
131
|
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
132
|
+
* WARNING: Wrong activation causes cascading hallucination across ALL SRS
|
|
133
|
+
* files. Each feature adds 2-3 conditional modules that downstream LLMs
|
|
134
|
+
* MUST fill with content — if the user never requested the feature, those
|
|
135
|
+
* modules get filled with hallucinated requirements.
|
|
135
136
|
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
137
|
+
* DEFAULT IS EMPTY ARRAY []. Most projects need no features.
|
|
138
|
+
*
|
|
139
|
+
* Activation rule: Include ONLY if the user used exact trigger keywords:
|
|
140
|
+
*
|
|
141
|
+
* - "file-storage": user said "file upload", "attachment", "image upload"
|
|
142
|
+
* - "real-time": user said "real-time", "WebSocket", "live updates", "chat"
|
|
143
|
+
* - "external-integration": user said "payment", "Stripe", "OAuth", "email
|
|
144
|
+
* service"
|
|
145
|
+
*
|
|
146
|
+
* Standard CRUD with auth = features: []. Do NOT activate features based on
|
|
147
|
+
* inference or general context.
|
|
138
148
|
*/
|
|
139
149
|
features: FixedAnalyzeTemplateFeature[];
|
|
140
150
|
}
|
|
@@ -219,8 +219,6 @@ export const validateErrorCodes = (props: {
|
|
|
219
219
|
return { canonical, references, undefinedReferences, parseErrors };
|
|
220
220
|
};
|
|
221
221
|
|
|
222
|
-
// ─── Legacy exports (kept for backward compatibility) ───
|
|
223
|
-
|
|
224
222
|
/**
|
|
225
223
|
* Detect error code conflicts across files.
|
|
226
224
|
*
|
|
@@ -304,21 +302,3 @@ export const buildFileErrorCodeConflictMap = (
|
|
|
304
302
|
|
|
305
303
|
return map;
|
|
306
304
|
};
|
|
307
|
-
|
|
308
|
-
/** @deprecated Bridge block registry removed. Use validateErrorCodes() instead. */
|
|
309
|
-
export const buildErrorCodeRegistry = (props: {
|
|
310
|
-
files: Array<{
|
|
311
|
-
file: AutoBeAnalyze.IFileScenario;
|
|
312
|
-
sectionEvents: AutoBeAnalyzeWriteSectionEvent[][];
|
|
313
|
-
}>;
|
|
314
|
-
}): IErrorCodeRegistryEntry[] => {
|
|
315
|
-
const result = validateErrorCodes(props);
|
|
316
|
-
return result.canonical;
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
/** @deprecated Bridge block injection removed. */
|
|
320
|
-
export const formatErrorCodeRegistryForPrompt = (
|
|
321
|
-
_registry: IErrorCodeRegistryEntry[],
|
|
322
|
-
): string => {
|
|
323
|
-
return "";
|
|
324
|
-
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// ─── Types ───
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A single extracted decision from one file's section content.
|
|
5
|
+
*
|
|
6
|
+
* Decisions are binary/discrete choices embedded in prose, e.g.:
|
|
7
|
+
*
|
|
8
|
+
* - "password change requires current password" → topic: "password_change",
|
|
9
|
+
* decision: "requires_current_password", value: "yes"
|
|
10
|
+
* - "deleted email can be reused" → topic: "deleted_email", decision:
|
|
11
|
+
* "can_be_reused", value: "yes"
|
|
12
|
+
*/
|
|
13
|
+
export interface IExtractedDecision {
|
|
14
|
+
/** Normalized topic grouping (e.g., "password_change", "email_reuse") */
|
|
15
|
+
topic: string;
|
|
16
|
+
|
|
17
|
+
/** Specific decision within the topic (e.g., "requires_current_password") */
|
|
18
|
+
decision: string;
|
|
19
|
+
|
|
20
|
+
/** The value of the decision (e.g., "yes", "no", "soft_delete", "hard_delete") */
|
|
21
|
+
value: string;
|
|
22
|
+
|
|
23
|
+
/** Evidence quote from the source text */
|
|
24
|
+
evidence: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Decisions extracted from a single file, returned by the extraction LLM. */
|
|
28
|
+
export interface IFileDecisions {
|
|
29
|
+
/** The filename this was extracted from */
|
|
30
|
+
filename: string;
|
|
31
|
+
|
|
32
|
+
/** All decisions extracted from this file */
|
|
33
|
+
decisions: IExtractedDecision[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A conflict between two or more files that state different values for the same
|
|
38
|
+
* topic+decision.
|
|
39
|
+
*/
|
|
40
|
+
export interface IDecisionConflict {
|
|
41
|
+
/** The topic of the conflict */
|
|
42
|
+
topic: string;
|
|
43
|
+
|
|
44
|
+
/** The specific decision that conflicts */
|
|
45
|
+
decision: string;
|
|
46
|
+
|
|
47
|
+
/** All differing values with their source files and evidence */
|
|
48
|
+
values: Array<{
|
|
49
|
+
value: string;
|
|
50
|
+
files: string[];
|
|
51
|
+
evidence: string;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── Main Detection ───
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Detect decision-level conflicts across all files.
|
|
59
|
+
*
|
|
60
|
+
* Groups all extracted decisions by `topic + decision` key, then finds cases
|
|
61
|
+
* where different files assign different values to the same key.
|
|
62
|
+
*
|
|
63
|
+
* This catches prose-level contradictions like:
|
|
64
|
+
*
|
|
65
|
+
* - File A: "password change requires current password" (yes)
|
|
66
|
+
* - File B: "password change does not require current password" (no)
|
|
67
|
+
*/
|
|
68
|
+
export const detectDecisionConflicts = (props: {
|
|
69
|
+
fileDecisions: IFileDecisions[];
|
|
70
|
+
}): IDecisionConflict[] => {
|
|
71
|
+
// Group by topic+decision
|
|
72
|
+
const groups: Map<
|
|
73
|
+
string,
|
|
74
|
+
Array<{ value: string; filename: string; evidence: string }>
|
|
75
|
+
> = new Map();
|
|
76
|
+
|
|
77
|
+
for (const { filename, decisions } of props.fileDecisions) {
|
|
78
|
+
for (const d of decisions) {
|
|
79
|
+
const key = `${normalizeKey(d.topic)}::${normalizeKey(d.decision)}`;
|
|
80
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
81
|
+
groups.get(key)!.push({
|
|
82
|
+
value: normalizeValue(d.value),
|
|
83
|
+
filename,
|
|
84
|
+
evidence: d.evidence,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Find conflicts: same key, different values
|
|
90
|
+
const conflicts: IDecisionConflict[] = [];
|
|
91
|
+
|
|
92
|
+
for (const [key, entries] of groups) {
|
|
93
|
+
// Group entries by value
|
|
94
|
+
const byValue: Map<
|
|
95
|
+
string,
|
|
96
|
+
Array<{ filename: string; evidence: string }>
|
|
97
|
+
> = new Map();
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
if (!byValue.has(entry.value)) byValue.set(entry.value, []);
|
|
100
|
+
byValue.get(entry.value)!.push({
|
|
101
|
+
filename: entry.filename,
|
|
102
|
+
evidence: entry.evidence,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// If more than one distinct value exists → conflict
|
|
107
|
+
if (byValue.size <= 1) continue;
|
|
108
|
+
|
|
109
|
+
const [topic, decision] = key.split("::");
|
|
110
|
+
conflicts.push({
|
|
111
|
+
topic: topic!,
|
|
112
|
+
decision: decision!,
|
|
113
|
+
values: [...byValue.entries()].map(([value, sources]) => ({
|
|
114
|
+
value,
|
|
115
|
+
files: sources.map((s) => s.filename),
|
|
116
|
+
evidence: sources[0]?.evidence ?? "",
|
|
117
|
+
})),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return conflicts;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build a map from filename → list of decision conflict feedback strings.
|
|
126
|
+
*
|
|
127
|
+
* Each file involved in a conflict gets feedback describing the contradiction.
|
|
128
|
+
*/
|
|
129
|
+
export const buildFileDecisionConflictMap = (
|
|
130
|
+
conflicts: IDecisionConflict[],
|
|
131
|
+
): Map<string, string[]> => {
|
|
132
|
+
const map: Map<string, string[]> = new Map();
|
|
133
|
+
|
|
134
|
+
for (const conflict of conflicts) {
|
|
135
|
+
const valueSummary = conflict.values
|
|
136
|
+
.map((v) => `"${v.value}" in [${v.files.join(", ")}]`)
|
|
137
|
+
.join(" vs ");
|
|
138
|
+
|
|
139
|
+
const feedback =
|
|
140
|
+
`Decision conflict: ${conflict.topic}.${conflict.decision} — ${valueSummary}. ` +
|
|
141
|
+
`Files must agree on this decision. Align with the canonical source.`;
|
|
142
|
+
|
|
143
|
+
// Add feedback to ALL files involved in this conflict
|
|
144
|
+
for (const valueGroup of conflict.values) {
|
|
145
|
+
for (const filename of valueGroup.files) {
|
|
146
|
+
if (!map.has(filename)) map.set(filename, []);
|
|
147
|
+
map.get(filename)!.push(feedback);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return map;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// ─── Helpers ───
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Normalize a key string for grouping: lowercase, replace whitespace/special
|
|
159
|
+
* chars with underscore, trim.
|
|
160
|
+
*/
|
|
161
|
+
function normalizeKey(s: string): string {
|
|
162
|
+
return s
|
|
163
|
+
.toLowerCase()
|
|
164
|
+
.trim()
|
|
165
|
+
.replace(/[\s\-\.]+/g, "_")
|
|
166
|
+
.replace(/[^a-z0-9_]/g, "");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Normalize a value for comparison: lowercase, trim, collapse whitespace. */
|
|
170
|
+
function normalizeValue(s: string): string {
|
|
171
|
+
return s.toLowerCase().trim().replace(/\s+/g, " ");
|
|
172
|
+
}
|
|
@@ -36,8 +36,7 @@ export async function orchestrateInterfaceSchemaRefine(
|
|
|
36
36
|
.filter(
|
|
37
37
|
([k, v]) =>
|
|
38
38
|
AutoBeJsonSchemaValidator.isPreset(k) === false &&
|
|
39
|
-
AutoBeOpenApiTypeChecker.isObject(v)
|
|
40
|
-
Object.keys(v.properties).length !== 0,
|
|
39
|
+
AutoBeOpenApiTypeChecker.isObject(v),
|
|
41
40
|
)
|
|
42
41
|
.map(([k]) => k);
|
|
43
42
|
props.progress.total += typeNames.length;
|