@autobe/agent 0.30.1 → 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.
Files changed (81) hide show
  1. package/lib/AutoBeMockAgent.js +0 -2
  2. package/lib/AutoBeMockAgent.js.map +1 -1
  3. package/lib/constants/AutoBeConfigConstant.d.ts +1 -1
  4. package/lib/constants/AutoBeSystemPromptConstant.d.ts +12 -11
  5. package/lib/constants/AutoBeSystemPromptConstant.js.map +1 -1
  6. package/lib/index.mjs +498 -55
  7. package/lib/index.mjs.map +1 -1
  8. package/lib/orchestrate/analyze/histories/transformAnalyzeExtractDecisionsHistory.d.ts +18 -0
  9. package/lib/orchestrate/analyze/histories/transformAnalyzeExtractDecisionsHistory.js +51 -0
  10. package/lib/orchestrate/analyze/histories/transformAnalyzeExtractDecisionsHistory.js.map +1 -0
  11. package/lib/orchestrate/analyze/histories/transformAnalyzeScenarioHistory.js +1 -1
  12. package/lib/orchestrate/analyze/histories/transformAnalyzeScenarioHistory.js.map +1 -1
  13. package/lib/orchestrate/analyze/histories/transformAnalyzeScenarioReviewHistory.js +1 -1
  14. package/lib/orchestrate/analyze/histories/transformAnalyzeScenarioReviewHistory.js.map +1 -1
  15. package/lib/orchestrate/analyze/histories/transformAnalyzeSectionCrossFileReviewHistory.d.ts +1 -0
  16. package/lib/orchestrate/analyze/histories/transformAnalyzeSectionCrossFileReviewHistory.js +15 -1
  17. package/lib/orchestrate/analyze/histories/transformAnalyzeSectionCrossFileReviewHistory.js.map +1 -1
  18. package/lib/orchestrate/analyze/histories/transformAnalyzeSectionReviewHistory.js +1 -1
  19. package/lib/orchestrate/analyze/histories/transformAnalyzeSectionReviewHistory.js.map +1 -1
  20. package/lib/orchestrate/analyze/orchestrateAnalyze.js +48 -13
  21. package/lib/orchestrate/analyze/orchestrateAnalyze.js.map +1 -1
  22. package/lib/orchestrate/analyze/orchestrateAnalyzeExtractDecisions.d.ts +17 -0
  23. package/lib/orchestrate/analyze/orchestrateAnalyzeExtractDecisions.js +345 -0
  24. package/lib/orchestrate/analyze/orchestrateAnalyzeExtractDecisions.js.map +1 -0
  25. package/lib/orchestrate/analyze/orchestrateAnalyzeScenario.js +2 -2
  26. package/lib/orchestrate/analyze/orchestrateAnalyzeSectionCrossFileReview.d.ts +1 -0
  27. package/lib/orchestrate/analyze/orchestrateAnalyzeSectionCrossFileReview.js +1 -0
  28. package/lib/orchestrate/analyze/orchestrateAnalyzeSectionCrossFileReview.js.map +1 -1
  29. package/lib/orchestrate/analyze/structures/IAutoBeAnalyzeExtractDecisionsApplication.d.ts +91 -0
  30. package/lib/orchestrate/analyze/structures/IAutoBeAnalyzeExtractDecisionsApplication.js +3 -0
  31. package/lib/orchestrate/analyze/structures/IAutoBeAnalyzeExtractDecisionsApplication.js.map +1 -0
  32. package/lib/orchestrate/analyze/structures/IAutoBeAnalyzeScenarioApplication.d.ts +15 -5
  33. package/lib/orchestrate/analyze/utils/buildErrorCodeRegistry.d.ts +0 -9
  34. package/lib/orchestrate/analyze/utils/buildErrorCodeRegistry.js +1 -13
  35. package/lib/orchestrate/analyze/utils/buildErrorCodeRegistry.js.map +1 -1
  36. package/lib/orchestrate/analyze/utils/detectDecisionConflicts.d.ts +63 -0
  37. package/lib/orchestrate/analyze/utils/detectDecisionConflicts.js +105 -0
  38. package/lib/orchestrate/analyze/utils/detectDecisionConflicts.js.map +1 -0
  39. package/lib/orchestrate/common/histories/transformPreliminaryHistory.js +1 -1
  40. package/lib/orchestrate/common/histories/transformPreliminaryHistory.js.map +1 -1
  41. package/lib/orchestrate/interface/histories/transformInterfaceActionEndpointReviewHistory.js +1 -1
  42. package/lib/orchestrate/interface/histories/transformInterfaceActionEndpointReviewHistory.js.map +1 -1
  43. package/lib/orchestrate/interface/histories/transformInterfaceBaseEndpointReviewHistory.js +1 -1
  44. package/lib/orchestrate/interface/histories/transformInterfaceBaseEndpointReviewHistory.js.map +1 -1
  45. package/lib/orchestrate/interface/histories/transformInterfaceSchemaRefineHistory.js +1 -1
  46. package/lib/orchestrate/interface/histories/transformInterfaceSchemaRefineHistory.js.map +1 -1
  47. package/lib/orchestrate/interface/orchestrateInterfaceSchemaRefine.js +1 -2
  48. package/lib/orchestrate/interface/orchestrateInterfaceSchemaRefine.js.map +1 -1
  49. package/lib/orchestrate/interface/orchestrateInterfaceSchemaReview.js +1 -2
  50. package/lib/orchestrate/interface/orchestrateInterfaceSchemaReview.js.map +1 -1
  51. package/lib/orchestrate/interface/utils/AutoBeJsonSchemaValidator.js +36 -0
  52. package/lib/orchestrate/interface/utils/AutoBeJsonSchemaValidator.js.map +1 -1
  53. package/lib/orchestrate/prisma/histories/transformPrismaComponentReviewHistory.js +2 -2
  54. package/lib/orchestrate/prisma/histories/transformPrismaComponentReviewHistory.js.map +1 -1
  55. package/lib/orchestrate/prisma/histories/transformPrismaComponentsHistory.js +1 -1
  56. package/lib/orchestrate/prisma/histories/transformPrismaComponentsHistory.js.map +1 -1
  57. package/lib/orchestrate/prisma/histories/transformPrismaGroupHistory.js +1 -1
  58. package/lib/orchestrate/prisma/histories/transformPrismaGroupHistory.js.map +1 -1
  59. package/lib/orchestrate/prisma/histories/transformPrismaGroupReviewHistory.js +1 -1
  60. package/lib/orchestrate/prisma/histories/transformPrismaGroupReviewHistory.js.map +1 -1
  61. package/lib/orchestrate/prisma/histories/transformPrismaSchemaHistory.js +1 -1
  62. package/lib/orchestrate/prisma/histories/transformPrismaSchemaHistory.js.map +1 -1
  63. package/lib/orchestrate/prisma/histories/transformPrismaSchemaReviewHistory.js +1 -1
  64. package/lib/orchestrate/prisma/histories/transformPrismaSchemaReviewHistory.js.map +1 -1
  65. package/lib/orchestrate/prisma/orchestratePrismaCorrect.js +1 -1
  66. package/package.json +5 -5
  67. package/src/AutoBeMockAgent.ts +0 -2
  68. package/src/constants/AutoBeConfigConstant.ts +1 -1
  69. package/src/constants/AutoBeSystemPromptConstant.ts +12 -11
  70. package/src/orchestrate/analyze/histories/transformAnalyzeExtractDecisionsHistory.ts +69 -0
  71. package/src/orchestrate/analyze/histories/transformAnalyzeSectionCrossFileReviewHistory.ts +20 -0
  72. package/src/orchestrate/analyze/orchestrateAnalyze.ts +58 -1
  73. package/src/orchestrate/analyze/orchestrateAnalyzeExtractDecisions.ts +97 -0
  74. package/src/orchestrate/analyze/orchestrateAnalyzeSectionCrossFileReview.ts +2 -0
  75. package/src/orchestrate/analyze/structures/IAutoBeAnalyzeExtractDecisionsApplication.ts +99 -0
  76. package/src/orchestrate/analyze/structures/IAutoBeAnalyzeScenarioApplication.ts +15 -5
  77. package/src/orchestrate/analyze/utils/buildErrorCodeRegistry.ts +0 -20
  78. package/src/orchestrate/analyze/utils/detectDecisionConflicts.ts +172 -0
  79. package/src/orchestrate/interface/orchestrateInterfaceSchemaRefine.ts +1 -2
  80. package/src/orchestrate/interface/orchestrateInterfaceSchemaReview.ts +1 -2
  81. 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
- * Selected from a FIXED catalog the LLM must NOT invent features outside
133
- * the predefined list. Each feature activates additional modules in the
134
- * appropriate SRS files.
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
- * If the project has no special features beyond REST CRUD, return an empty
137
- * array.
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;