@uluops/core 0.5.0

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 (167) hide show
  1. package/README.md +543 -0
  2. package/definitions/starter/code-validator.agent.yaml +134 -0
  3. package/definitions/starter/docs-validator.agent.yaml +142 -0
  4. package/definitions/starter/public-interface-validator.agent.yaml +138 -0
  5. package/definitions/starter/security-analyst.agent.yaml +144 -0
  6. package/definitions/starter/test-architect.agent.yaml +137 -0
  7. package/dist/ai/AIProvider.d.ts +198 -0
  8. package/dist/ai/AIProvider.d.ts.map +1 -0
  9. package/dist/ai/AIProvider.js +557 -0
  10. package/dist/ai/AIProvider.js.map +1 -0
  11. package/dist/ai/ModelCatalog.d.ts +78 -0
  12. package/dist/ai/ModelCatalog.d.ts.map +1 -0
  13. package/dist/ai/ModelCatalog.js +193 -0
  14. package/dist/ai/ModelCatalog.js.map +1 -0
  15. package/dist/ai/ShellExecutor.d.ts +42 -0
  16. package/dist/ai/ShellExecutor.d.ts.map +1 -0
  17. package/dist/ai/ShellExecutor.js +62 -0
  18. package/dist/ai/ShellExecutor.js.map +1 -0
  19. package/dist/ai/TokenBudgetTracker.d.ts +49 -0
  20. package/dist/ai/TokenBudgetTracker.d.ts.map +1 -0
  21. package/dist/ai/TokenBudgetTracker.js +61 -0
  22. package/dist/ai/TokenBudgetTracker.js.map +1 -0
  23. package/dist/ai/ToolAdapter.d.ts +25 -0
  24. package/dist/ai/ToolAdapter.d.ts.map +1 -0
  25. package/dist/ai/ToolAdapter.js +135 -0
  26. package/dist/ai/ToolAdapter.js.map +1 -0
  27. package/dist/ai/index.d.ts +6 -0
  28. package/dist/ai/index.d.ts.map +1 -0
  29. package/dist/ai/index.js +4 -0
  30. package/dist/ai/index.js.map +1 -0
  31. package/dist/client/UluOpsClient.d.ts +111 -0
  32. package/dist/client/UluOpsClient.d.ts.map +1 -0
  33. package/dist/client/UluOpsClient.js +329 -0
  34. package/dist/client/UluOpsClient.js.map +1 -0
  35. package/dist/constants.d.ts +6 -0
  36. package/dist/constants.d.ts.map +1 -0
  37. package/dist/constants.js +9 -0
  38. package/dist/constants.js.map +1 -0
  39. package/dist/errors/UluOpsError.d.ts +10 -0
  40. package/dist/errors/UluOpsError.d.ts.map +1 -0
  41. package/dist/errors/UluOpsError.js +13 -0
  42. package/dist/errors/UluOpsError.js.map +1 -0
  43. package/dist/errors/index.d.ts +64 -0
  44. package/dist/errors/index.d.ts.map +1 -0
  45. package/dist/errors/index.js +93 -0
  46. package/dist/errors/index.js.map +1 -0
  47. package/dist/executor/AgentExecutor.d.ts +57 -0
  48. package/dist/executor/AgentExecutor.d.ts.map +1 -0
  49. package/dist/executor/AgentExecutor.js +331 -0
  50. package/dist/executor/AgentExecutor.js.map +1 -0
  51. package/dist/executor/CommandExecutor.d.ts +33 -0
  52. package/dist/executor/CommandExecutor.d.ts.map +1 -0
  53. package/dist/executor/CommandExecutor.js +183 -0
  54. package/dist/executor/CommandExecutor.js.map +1 -0
  55. package/dist/executor/PipelineExecutor.d.ts +55 -0
  56. package/dist/executor/PipelineExecutor.d.ts.map +1 -0
  57. package/dist/executor/PipelineExecutor.js +273 -0
  58. package/dist/executor/PipelineExecutor.js.map +1 -0
  59. package/dist/executor/ToolHandler.d.ts +47 -0
  60. package/dist/executor/ToolHandler.d.ts.map +1 -0
  61. package/dist/executor/ToolHandler.js +615 -0
  62. package/dist/executor/ToolHandler.js.map +1 -0
  63. package/dist/executor/WorkflowExecutor.d.ts +55 -0
  64. package/dist/executor/WorkflowExecutor.d.ts.map +1 -0
  65. package/dist/executor/WorkflowExecutor.js +368 -0
  66. package/dist/executor/WorkflowExecutor.js.map +1 -0
  67. package/dist/executor/preflight.d.ts +8 -0
  68. package/dist/executor/preflight.d.ts.map +1 -0
  69. package/dist/executor/preflight.js +102 -0
  70. package/dist/executor/preflight.js.map +1 -0
  71. package/dist/executor/symbols.d.ts +13 -0
  72. package/dist/executor/symbols.d.ts.map +1 -0
  73. package/dist/executor/symbols.js +102 -0
  74. package/dist/executor/symbols.js.map +1 -0
  75. package/dist/index.d.ts +32 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +25 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/parser/OutputExtractor.d.ts +52 -0
  80. package/dist/parser/OutputExtractor.d.ts.map +1 -0
  81. package/dist/parser/OutputExtractor.js +818 -0
  82. package/dist/parser/OutputExtractor.js.map +1 -0
  83. package/dist/parser/outputSchemas.d.ts +223 -0
  84. package/dist/parser/outputSchemas.d.ts.map +1 -0
  85. package/dist/parser/outputSchemas.js +73 -0
  86. package/dist/parser/outputSchemas.js.map +1 -0
  87. package/dist/registry/RegistryClient.d.ts +75 -0
  88. package/dist/registry/RegistryClient.d.ts.map +1 -0
  89. package/dist/registry/RegistryClient.js +419 -0
  90. package/dist/registry/RegistryClient.js.map +1 -0
  91. package/dist/registry/index.d.ts +2 -0
  92. package/dist/registry/index.d.ts.map +1 -0
  93. package/dist/registry/index.js +2 -0
  94. package/dist/registry/index.js.map +1 -0
  95. package/dist/types/agent.d.ts +406 -0
  96. package/dist/types/agent.d.ts.map +1 -0
  97. package/dist/types/agent.js +2 -0
  98. package/dist/types/agent.js.map +1 -0
  99. package/dist/types/ai.d.ts +14 -0
  100. package/dist/types/ai.d.ts.map +1 -0
  101. package/dist/types/ai.js +2 -0
  102. package/dist/types/ai.js.map +1 -0
  103. package/dist/types/command.d.ts +153 -0
  104. package/dist/types/command.d.ts.map +1 -0
  105. package/dist/types/command.js +2 -0
  106. package/dist/types/command.js.map +1 -0
  107. package/dist/types/config.d.ts +136 -0
  108. package/dist/types/config.d.ts.map +1 -0
  109. package/dist/types/config.js +2 -0
  110. package/dist/types/config.js.map +1 -0
  111. package/dist/types/execution.d.ts +172 -0
  112. package/dist/types/execution.d.ts.map +1 -0
  113. package/dist/types/execution.js +2 -0
  114. package/dist/types/execution.js.map +1 -0
  115. package/dist/types/index.d.ts +12 -0
  116. package/dist/types/index.d.ts.map +1 -0
  117. package/dist/types/index.js +2 -0
  118. package/dist/types/index.js.map +1 -0
  119. package/dist/types/parser.d.ts +75 -0
  120. package/dist/types/parser.d.ts.map +1 -0
  121. package/dist/types/parser.js +2 -0
  122. package/dist/types/parser.js.map +1 -0
  123. package/dist/types/pipeline.d.ts +155 -0
  124. package/dist/types/pipeline.d.ts.map +1 -0
  125. package/dist/types/pipeline.js +2 -0
  126. package/dist/types/pipeline.js.map +1 -0
  127. package/dist/types/registry.d.ts +232 -0
  128. package/dist/types/registry.d.ts.map +1 -0
  129. package/dist/types/registry.js +2 -0
  130. package/dist/types/registry.js.map +1 -0
  131. package/dist/types/tools.d.ts +29 -0
  132. package/dist/types/tools.d.ts.map +1 -0
  133. package/dist/types/tools.js +2 -0
  134. package/dist/types/tools.js.map +1 -0
  135. package/dist/types/validation.d.ts +237 -0
  136. package/dist/types/validation.d.ts.map +1 -0
  137. package/dist/types/validation.js +2 -0
  138. package/dist/types/validation.js.map +1 -0
  139. package/dist/types/workflow.d.ts +131 -0
  140. package/dist/types/workflow.d.ts.map +1 -0
  141. package/dist/types/workflow.js +2 -0
  142. package/dist/types/workflow.js.map +1 -0
  143. package/dist/utils/formatError.d.ts +6 -0
  144. package/dist/utils/formatError.d.ts.map +1 -0
  145. package/dist/utils/formatError.js +10 -0
  146. package/dist/utils/formatError.js.map +1 -0
  147. package/dist/utils/parseRef.d.ts +11 -0
  148. package/dist/utils/parseRef.d.ts.map +1 -0
  149. package/dist/utils/parseRef.js +16 -0
  150. package/dist/utils/parseRef.js.map +1 -0
  151. package/dist/utils/sumTokenMetrics.d.ts +9 -0
  152. package/dist/utils/sumTokenMetrics.d.ts.map +1 -0
  153. package/dist/utils/sumTokenMetrics.js +20 -0
  154. package/dist/utils/sumTokenMetrics.js.map +1 -0
  155. package/dist/utils/topoSort.d.ts +24 -0
  156. package/dist/utils/topoSort.d.ts.map +1 -0
  157. package/dist/utils/topoSort.js +60 -0
  158. package/dist/utils/topoSort.js.map +1 -0
  159. package/dist/validation/ValidationClient.d.ts +51 -0
  160. package/dist/validation/ValidationClient.d.ts.map +1 -0
  161. package/dist/validation/ValidationClient.js +179 -0
  162. package/dist/validation/ValidationClient.js.map +1 -0
  163. package/dist/validation/index.d.ts +2 -0
  164. package/dist/validation/index.d.ts.map +1 -0
  165. package/dist/validation/index.js +2 -0
  166. package/dist/validation/index.js.map +1 -0
  167. package/package.json +76 -0
@@ -0,0 +1,818 @@
1
+ import { ParseError } from '../errors/index.js';
2
+ /**
3
+ * Extracts structured output from LLM responses using a 3-strategy fallback:
4
+ * 1. JSON code fence (highest confidence)
5
+ * 2. Inline JSON detection
6
+ * 3. Structured text pattern matching (lowest confidence)
7
+ */
8
+ export class OutputExtractor {
9
+ static INLINE_JSON_PATTERN = /\{[\s\S]*?(?:"decision"|"status"|"score")[\s\S]*?\}/;
10
+ static STRUCTURED_PATTERNS = {
11
+ decision: /(?:decision|status|result)\s*[:=]\s*["']?(\w+)["']?/i,
12
+ // Section-header style: "DECISION" on its own line followed by separator, then decision value
13
+ sectionDecision: /^DECISION\s*\n[━═─\-]+\n+[✅❌⚠️🔴🟡]*\s*(PASS|FAIL|WARN|WARNING|ERROR|SHIP|REJECT|SKIP)\b/im,
14
+ // Emoji-prefixed: "✅ PASS" anywhere in text
15
+ emojiDecision: /[✅❌⚠️🔴🟡🟢]\s*(PASS|FAIL|WARN|WARNING|ERROR|SHIP|REJECT)\b/i,
16
+ score: /(?:score|points)\s*[:=]\s*(\d+(?:\.\d+)?)/i,
17
+ // Score with denominator: "95/100"
18
+ scoreFraction: /(?:score|points)\s*[:=]?\s*(\d+)\s*\/\s*(\d+)/i,
19
+ maxScore: /(?:max(?:imum)?[\s_]?score|out[\s_]?of|total)\s*[:=]\s*(\d+)/i,
20
+ // Issue line: "- description: file/path.ts:123 [CODE]"
21
+ issueLine: /^[\s]*[-•🟡🔴🟠🔵]\s+(.+?):\s+([\w/.-]+\.(?:ts|js|tsx|jsx|py|go|rs|java|rb|css|html|json|yaml|yml|toml|md)):(\d+)\s*(?:\[([^\]]+)\])?/gm,
22
+ };
23
+ /**
24
+ * Extract structured output from LLM response text
25
+ */
26
+ extract(content, agentType, options = {}) {
27
+ const result = this.extractWithMetadata(content, agentType, options);
28
+ return result.output;
29
+ }
30
+ /**
31
+ * Extract with full metadata about extraction method and confidence
32
+ */
33
+ extractWithMetadata(content, agentType, options = {}) {
34
+ const warnings = [];
35
+ // Strategy 1: Try JSON code fence (highest confidence)
36
+ const fenceResult = this.extractFromCodeFence(content, options);
37
+ if (fenceResult) {
38
+ return {
39
+ output: this.normalizeOutput(fenceResult, agentType),
40
+ method: 'json_code_fence',
41
+ confidence: 0.95,
42
+ warnings,
43
+ };
44
+ }
45
+ // Strategy 1b: Try parsing trimmed content as whole JSON object
46
+ const wholeJsonResult = this.extractWholeJson(content);
47
+ if (wholeJsonResult) {
48
+ return {
49
+ output: this.normalizeOutput(wholeJsonResult, agentType),
50
+ method: 'inline_json',
51
+ confidence: 0.9,
52
+ warnings,
53
+ };
54
+ }
55
+ // Strategy 2: Try inline JSON detection
56
+ const inlineResult = this.extractInlineJson(content);
57
+ if (inlineResult) {
58
+ warnings.push('Extracted from inline JSON - consider using code fence for reliability');
59
+ return {
60
+ output: this.normalizeOutput(inlineResult, agentType),
61
+ method: 'inline_json',
62
+ confidence: 0.75,
63
+ warnings,
64
+ };
65
+ }
66
+ // Strategy 3: Fall back to structured text parsing
67
+ const textResult = this.extractFromStructuredText(content, agentType);
68
+ if (textResult) {
69
+ warnings.push('Extracted from structured text patterns - JSON output recommended');
70
+ return {
71
+ output: textResult,
72
+ method: 'structured_text',
73
+ confidence: 0.5,
74
+ warnings,
75
+ };
76
+ }
77
+ // Extraction failed
78
+ if (options.strict) {
79
+ throw new ParseError('Failed to extract structured output from response', content.substring(0, 500));
80
+ }
81
+ return {
82
+ output: {
83
+ decision: 'ERROR',
84
+ score: 0,
85
+ },
86
+ method: 'structured_text',
87
+ confidence: 0,
88
+ warnings: ['Could not extract structured output from response'],
89
+ };
90
+ }
91
+ extractFromCodeFence(content, options) {
92
+ const lang = options.codeFenceLanguage ?? 'json';
93
+ const pattern = new RegExp(`\`\`\`(?:${lang})?\\s*\\n([\\s\\S]*?)\\n\`\`\``, 'g');
94
+ const matches = [...content.matchAll(pattern)];
95
+ if (matches.length === 0)
96
+ return null;
97
+ const lastMatch = matches[matches.length - 1];
98
+ if (!lastMatch?.[1])
99
+ return null;
100
+ try {
101
+ return JSON.parse(lastMatch[1].trim());
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ }
107
+ extractWholeJson(content) {
108
+ const trimmed = content.trim();
109
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}'))
110
+ return null;
111
+ try {
112
+ const parsed = JSON.parse(trimmed);
113
+ if (typeof parsed === 'object' && parsed !== null)
114
+ return parsed;
115
+ }
116
+ catch {
117
+ // Not valid JSON
118
+ }
119
+ return null;
120
+ }
121
+ extractInlineJson(content) {
122
+ const match = content.match(OutputExtractor.INLINE_JSON_PATTERN);
123
+ if (!match)
124
+ return null;
125
+ // For multi-step LLM output, the final JSON report is often at the end.
126
+ // Find all valid JSON objects and pick the best one (largest with agent output fields).
127
+ const candidates = [];
128
+ let searchFrom = content.length - 1;
129
+ for (let found = 0; found < 50 && searchFrom >= 0; searchFrom--) {
130
+ if (content[searchFrom] === '{') {
131
+ const jsonStr = this.extractBalancedJson(content, searchFrom);
132
+ if (jsonStr && jsonStr.length >= 20) {
133
+ try {
134
+ const parsed = JSON.parse(jsonStr);
135
+ if (typeof parsed === 'object' && parsed !== null) {
136
+ candidates.push({ index: searchFrom, parsed, length: jsonStr.length });
137
+ }
138
+ }
139
+ catch { /* skip */ }
140
+ }
141
+ found++;
142
+ }
143
+ }
144
+ // Also try from the first regex match
145
+ const firstMatchIndex = content.indexOf(match[0]);
146
+ if (!candidates.some(c => c.index === firstMatchIndex)) {
147
+ const jsonStr = this.extractBalancedJson(content, firstMatchIndex);
148
+ if (jsonStr && jsonStr.length >= 20) {
149
+ try {
150
+ const parsed = JSON.parse(jsonStr);
151
+ if (typeof parsed === 'object' && parsed !== null) {
152
+ candidates.push({ index: firstMatchIndex, parsed, length: jsonStr.length });
153
+ }
154
+ }
155
+ catch { /* skip */ }
156
+ }
157
+ }
158
+ if (candidates.length === 0)
159
+ return null;
160
+ // Prefer the largest JSON object — the final report is typically the biggest.
161
+ // Among ties, prefer objects with more agent-relevant fields.
162
+ const agentFields = ['decision', 'final_decision', 'score', 'status', 'categories',
163
+ 'validation_results', 'validation_summary', 'validations', 'validationResults',
164
+ 'breakdown', 'issues', 'issues_found', 'summary', 'recommendations'];
165
+ const scored = candidates.map(c => {
166
+ const fieldCount = agentFields.filter(f => f in c.parsed).length;
167
+ return { ...c, fieldCount };
168
+ });
169
+ // Sort by length desc (largest JSON object), then by field count desc
170
+ scored.sort((a, b) => b.length - a.length || b.fieldCount - a.fieldCount);
171
+ return scored[0].parsed;
172
+ }
173
+ extractBalancedJson(content, startIndex) {
174
+ let depth = 0;
175
+ let inString = false;
176
+ let escape = false;
177
+ for (let i = startIndex; i < content.length; i++) {
178
+ const char = content[i];
179
+ if (escape) {
180
+ escape = false;
181
+ continue;
182
+ }
183
+ if (char === '\\') {
184
+ escape = true;
185
+ continue;
186
+ }
187
+ if (char === '"') {
188
+ inString = !inString;
189
+ continue;
190
+ }
191
+ if (inString)
192
+ continue;
193
+ if (char === '{')
194
+ depth++;
195
+ if (char === '}') {
196
+ depth--;
197
+ if (depth === 0) {
198
+ return content.substring(startIndex, i + 1);
199
+ }
200
+ }
201
+ }
202
+ return null;
203
+ }
204
+ extractFromStructuredText(content, agentType) {
205
+ const patterns = OutputExtractor.STRUCTURED_PATTERNS;
206
+ // Try multiple decision patterns in priority order
207
+ const decisionMatch = content.match(patterns.decision)
208
+ ?? content.match(patterns.sectionDecision)
209
+ ?? content.match(patterns.emojiDecision);
210
+ const scoreMatch = content.match(patterns.scoreFraction)
211
+ ?? content.match(patterns.score);
212
+ if (!decisionMatch && !scoreMatch) {
213
+ return null;
214
+ }
215
+ const output = {
216
+ decision: decisionMatch
217
+ ? this.normalizeDecision(decisionMatch[1] ?? '', agentType)
218
+ : 'UNKNOWN',
219
+ };
220
+ if (scoreMatch?.[1]) {
221
+ output.score = parseFloat(scoreMatch[1]);
222
+ }
223
+ if (agentType === 'validator') {
224
+ // Extract maxScore from fraction pattern (95/100) or explicit pattern
225
+ if (scoreMatch?.[2]) {
226
+ output.maxScore = parseInt(scoreMatch[2], 10);
227
+ }
228
+ else {
229
+ const maxScoreMatch = content.match(patterns.maxScore);
230
+ if (maxScoreMatch?.[1]) {
231
+ output.maxScore = parseInt(maxScoreMatch[1], 10);
232
+ }
233
+ }
234
+ // Extract issues from structured text (warning/suggestion lines with file:line references)
235
+ const issues = this.extractIssuesFromText(content);
236
+ if (issues.length > 0) {
237
+ output.categories = [{
238
+ name: 'Extracted Issues',
239
+ score: output.score ?? 0,
240
+ maxPoints: output.maxScore ?? 100,
241
+ findings: [{
242
+ criterion: 'Text-extracted findings',
243
+ pointsEarned: 0,
244
+ pointsPossible: 0,
245
+ issues,
246
+ }],
247
+ }];
248
+ }
249
+ }
250
+ return output;
251
+ }
252
+ extractIssuesFromText(content) {
253
+ const issues = [];
254
+ const pattern = OutputExtractor.STRUCTURED_PATTERNS.issueLine;
255
+ // Reset lastIndex for global regex
256
+ pattern.lastIndex = 0;
257
+ let match;
258
+ while ((match = pattern.exec(content)) !== null) {
259
+ const [, title, filePath, lineStr, failureCode] = match;
260
+ if (title && filePath) {
261
+ issues.push({
262
+ title: title.trim(),
263
+ priority: this.inferPriorityFromContext(content, match.index),
264
+ severity: this.inferSeverityFromContext(content, match.index),
265
+ failureCode: failureCode?.trim(),
266
+ filePath,
267
+ lineNumber: lineStr ? parseInt(lineStr, 10) : undefined,
268
+ description: title.trim(),
269
+ });
270
+ }
271
+ }
272
+ return issues;
273
+ }
274
+ inferPriorityFromContext(content, matchIndex) {
275
+ // Look backwards from match for section headers
276
+ const preceding = content.slice(Math.max(0, matchIndex - 200), matchIndex).toLowerCase();
277
+ if (preceding.includes('critical') || preceding.includes('blocker') || preceding.includes('🔴'))
278
+ return 'critical';
279
+ if (preceding.includes('suggestion') || preceding.includes('consider') || preceding.includes('🔵'))
280
+ return 'backlog';
281
+ return 'suggested';
282
+ }
283
+ inferSeverityFromContext(content, matchIndex) {
284
+ const preceding = content.slice(Math.max(0, matchIndex - 200), matchIndex).toLowerCase();
285
+ if (preceding.includes('critical') || preceding.includes('🔴'))
286
+ return 'critical';
287
+ if (preceding.includes('warning') || preceding.includes('🟡'))
288
+ return 'medium';
289
+ if (preceding.includes('suggestion') || preceding.includes('🔵'))
290
+ return 'low';
291
+ return 'medium';
292
+ }
293
+ /** Resolved source objects from common nesting patterns. Reduces parameter passing across resolve methods. */
294
+ buildParseSources(obj) {
295
+ const result = this.asRecord(obj['result']);
296
+ const summary = this.asRecord(obj['summary']) ?? this.asRecord(result?.['summary']);
297
+ const report = this.asRecord(obj['report']);
298
+ const reportResults = this.asRecord(report?.['results']) ?? this.asRecord(obj['results']);
299
+ const reportSummary = this.asRecord(report?.['summary']) ?? this.asRecord(reportResults?.['summary']);
300
+ const validationSummary = this.findWrapperWithScoreOrDecision(obj);
301
+ return { obj, result, summary, report, reportResults, reportSummary, validationSummary };
302
+ }
303
+ normalizeOutput(raw, agentType) {
304
+ if (!raw || typeof raw !== 'object') {
305
+ return { decision: 'ERROR' };
306
+ }
307
+ const obj = raw;
308
+ const sources = this.buildParseSources(obj);
309
+ const output = {
310
+ decision: this.normalizeDecision(this.resolveDecisionField(sources), agentType),
311
+ rawJson: raw,
312
+ };
313
+ // Resolve score
314
+ const rawScore = this.resolveScoreField(sources);
315
+ if (typeof rawScore === 'number') {
316
+ output.score = rawScore;
317
+ }
318
+ else if (typeof rawScore === 'string') {
319
+ output.score = parseFloat(rawScore);
320
+ }
321
+ if (agentType === 'validator') {
322
+ this.resolveValidatorFields(output, sources);
323
+ }
324
+ if (agentType === 'executor' && Array.isArray(obj['artifacts'])) {
325
+ output.artifacts = this.parseArtifacts(obj['artifacts']);
326
+ }
327
+ return output;
328
+ }
329
+ resolveValidatorFields(output, sources) {
330
+ const { obj, result, summary, report } = sources;
331
+ // Resolve maxScore
332
+ const rawMaxScore = obj['maxScore'] ?? obj['max_score']
333
+ ?? result?.['max_score'] ?? result?.['maxScore']
334
+ ?? summary?.['max_score'] ?? summary?.['maxScore']
335
+ ?? obj['pass_threshold'];
336
+ if (typeof rawMaxScore === 'number') {
337
+ output.maxScore = rawMaxScore;
338
+ }
339
+ else if (typeof rawMaxScore === 'string') {
340
+ output.maxScore = parseInt(rawMaxScore, 10);
341
+ }
342
+ // Resolve categories
343
+ output.categories = this.resolveCategories(obj, result, report);
344
+ // If no score found but categories exist, sum category scores
345
+ if (output.score === undefined && output.categories && output.categories.length > 0) {
346
+ output.score = output.categories.reduce((sum, c) => sum + c.score, 0);
347
+ }
348
+ // Resolve flat issues and attach to categories
349
+ this.attachFlatIssues(output, sources);
350
+ }
351
+ attachFlatIssues(output, sources) {
352
+ const flatIssues = this.resolveIssuesFlat(sources.obj, sources.result, sources.report, sources.validationSummary);
353
+ if (flatIssues.length === 0)
354
+ return;
355
+ const issuesFinding = {
356
+ criterion: 'Extracted findings',
357
+ pointsEarned: 0,
358
+ pointsPossible: 0,
359
+ issues: flatIssues,
360
+ };
361
+ if (!output.categories || output.categories.length === 0) {
362
+ output.categories = [{
363
+ name: 'Extracted Issues',
364
+ score: output.score ?? 0,
365
+ maxPoints: output.maxScore ?? 100,
366
+ findings: [issuesFinding],
367
+ }];
368
+ }
369
+ else {
370
+ const emptyCategory = output.categories.find(c => c.findings.length === 0);
371
+ if (emptyCategory) {
372
+ emptyCategory.findings.push(issuesFinding);
373
+ }
374
+ else {
375
+ output.categories.push({
376
+ name: 'Extracted Issues',
377
+ score: output.score ?? 0,
378
+ maxPoints: output.maxScore ?? 100,
379
+ findings: [issuesFinding],
380
+ });
381
+ }
382
+ }
383
+ }
384
+ asRecord(value) {
385
+ return (value && typeof value === 'object' && !Array.isArray(value))
386
+ ? value
387
+ : undefined;
388
+ }
389
+ /**
390
+ * Scan all top-level object values for one that contains 'score' or 'decision'.
391
+ * Handles arbitrary wrapper names (validation, validations, validationResults, etc.)
392
+ * without whitelisting specific field names.
393
+ */
394
+ findWrapperWithScoreOrDecision(obj) {
395
+ // Skip known non-wrapper fields
396
+ const skip = new Set(['issues', 'categories', 'recommendations', 'evidence',
397
+ 'reasoning', 'reasoning_trace', 'notes', 'auto_fail_conditions', 'filesReviewed',
398
+ 'files_reviewed', 'artifacts', 'result', 'summary', 'report']);
399
+ for (const [key, value] of Object.entries(obj)) {
400
+ if (skip.has(key))
401
+ continue;
402
+ const rec = this.asRecord(value);
403
+ if (!rec)
404
+ continue;
405
+ if ('score' in rec || 'score_total' in rec || 'total_score' in rec || 'decision' in rec || 'status' in rec || 'breakdown' in rec || 'score_breakdown' in rec) {
406
+ return rec;
407
+ }
408
+ }
409
+ return undefined;
410
+ }
411
+ resolveDecisionField(ctx) {
412
+ const { obj } = ctx;
413
+ const sources = [ctx.obj, ctx.summary, ctx.result, ctx.report, ctx.reportResults, ctx.reportSummary, ctx.validationSummary];
414
+ // Check each source for decision/final_decision fields
415
+ for (const source of sources) {
416
+ if (!source)
417
+ continue;
418
+ for (const key of ['decision', 'final_decision']) {
419
+ const d = source[key];
420
+ if (typeof d === 'string')
421
+ return d;
422
+ // Handle decision as object: { pass: true, label: "PASS" } or { result: "PASS" }
423
+ if (d && typeof d === 'object') {
424
+ const dObj = d;
425
+ if (typeof dObj['result'] === 'string')
426
+ return dObj['result'];
427
+ if (typeof dObj['label'] === 'string')
428
+ return dObj['label'];
429
+ if (typeof dObj['value'] === 'string')
430
+ return dObj['value'];
431
+ if (typeof dObj['status'] === 'string')
432
+ return dObj['status'];
433
+ if (typeof dObj['pass'] === 'boolean')
434
+ return dObj['pass'] ? 'PASS' : 'FAIL';
435
+ }
436
+ }
437
+ }
438
+ // Fallback to status field
439
+ for (const source of sources) {
440
+ if (!source)
441
+ continue;
442
+ if (typeof source['status'] === 'string')
443
+ return source['status'];
444
+ }
445
+ // Fallback: check if summary is a string starting with a decision word
446
+ if (typeof obj['summary'] === 'string') {
447
+ const summaryFirst = obj['summary'].split(/[\s\-–—]+/)[0]?.toUpperCase();
448
+ if (summaryFirst && ['PASS', 'FAIL', 'WARN', 'ERROR', 'COMPLETE'].includes(summaryFirst)) {
449
+ return summaryFirst;
450
+ }
451
+ }
452
+ return 'UNKNOWN';
453
+ }
454
+ resolveScoreField(ctx) {
455
+ const { obj } = ctx;
456
+ const sources = [ctx.obj, ctx.summary, ctx.result, ctx.report, ctx.reportResults, ctx.reportSummary, ctx.validationSummary];
457
+ // Check each source for a score value
458
+ for (const source of sources) {
459
+ if (!source)
460
+ continue;
461
+ for (const scoreKey of ['score', 'total_score', 'score_total']) {
462
+ const s = source[scoreKey];
463
+ if (typeof s === 'number')
464
+ return s;
465
+ if (typeof s === 'string' && !isNaN(parseFloat(s)))
466
+ return s;
467
+ // Handle score as object: { total: 85, ... }
468
+ if (s && typeof s === 'object') {
469
+ const sObj = s;
470
+ for (const key of ['total', 'value', 'overall', 'final']) {
471
+ if (typeof sObj[key] === 'number')
472
+ return sObj[key];
473
+ if (typeof sObj[key] === 'string')
474
+ return sObj[key];
475
+ }
476
+ }
477
+ }
478
+ }
479
+ // Note: validationSummary (from findWrapperWithScoreOrDecision) is already in the sources loop above.
480
+ // Check scores object with named sub-scores (gpt-4.1-nano shape: { scores: { "Code Quality": 23, ... } })
481
+ const scores = this.asRecord(obj['scores']);
482
+ if (scores) {
483
+ if (typeof scores['Total'] === 'number')
484
+ return scores['Total'];
485
+ if (typeof scores['total'] === 'number')
486
+ return scores['total'];
487
+ }
488
+ // Check breakdown with sub-scores sum (search wrapper objects too)
489
+ const wrapper = this.findWrapperWithScoreOrDecision(obj);
490
+ const breakdown = this.asRecord(obj['breakdown'])
491
+ ?? this.asRecord(obj['score_breakdown'])
492
+ ?? this.asRecord(wrapper?.['breakdown'])
493
+ ?? this.asRecord(wrapper?.['score_breakdown']);
494
+ if (breakdown) {
495
+ const values = [];
496
+ for (const v of Object.values(breakdown)) {
497
+ if (typeof v === 'number') {
498
+ values.push(v);
499
+ continue;
500
+ }
501
+ // Handle { points: N, deductions: N } shape
502
+ const rec = this.asRecord(v);
503
+ if (rec && typeof rec['points'] === 'number') {
504
+ const deductions = typeof rec['deductions'] === 'number' ? rec['deductions'] : 0;
505
+ values.push(rec['points'] - deductions);
506
+ }
507
+ }
508
+ if (values.length > 0)
509
+ return values.reduce((a, b) => a + b, 0);
510
+ }
511
+ // Check criteria with sub-scores sum (gpt-4.1-nano shape)
512
+ const criteria = this.asRecord(obj['criteria']);
513
+ if (criteria) {
514
+ const values = [];
515
+ for (const v of Object.values(criteria)) {
516
+ if (typeof v === 'number') {
517
+ values.push(v);
518
+ continue;
519
+ }
520
+ const rec = this.asRecord(v);
521
+ if (rec && typeof rec['score'] === 'number')
522
+ values.push(rec['score']);
523
+ }
524
+ if (values.length > 0)
525
+ return values.reduce((a, b) => a + b, 0);
526
+ }
527
+ return undefined;
528
+ }
529
+ resolveCategories(obj, result, report) {
530
+ // Direct categories array
531
+ for (const source of [obj, result, report]) {
532
+ if (!source)
533
+ continue;
534
+ if (Array.isArray(source['categories'])) {
535
+ return this.parseCategories(source['categories']);
536
+ }
537
+ }
538
+ // Named scores object → synthetic categories (e.g., { scores: { "Code Quality": 23, ... } })
539
+ const scores = this.asRecord(obj['scores']) ?? this.asRecord(report?.['scores']);
540
+ if (scores) {
541
+ const cats = [];
542
+ for (const [name, value] of Object.entries(scores)) {
543
+ if (typeof value === 'number' && name !== 'Total' && name !== 'total' && name !== 'pass_threshold') {
544
+ cats.push({ name, score: value, maxPoints: 100, findings: [] });
545
+ }
546
+ }
547
+ if (cats.length > 0)
548
+ return cats;
549
+ }
550
+ // Breakdown object → synthetic categories
551
+ // Search top-level breakdown, score_breakdown, and inside any wrapper object
552
+ const wrapper = this.findWrapperWithScoreOrDecision(obj);
553
+ const breakdown = this.asRecord(obj['breakdown'])
554
+ ?? this.asRecord(obj['score_breakdown'])
555
+ ?? this.asRecord(wrapper?.['breakdown'])
556
+ ?? this.asRecord(wrapper?.['score_breakdown']);
557
+ if (breakdown) {
558
+ const cats = [];
559
+ for (const [name, value] of Object.entries(breakdown)) {
560
+ if (typeof value === 'number') {
561
+ cats.push({ name, score: value, maxPoints: 100, findings: [] });
562
+ }
563
+ else {
564
+ // Handle { points: N, deductions: N } shape
565
+ const rec = this.asRecord(value);
566
+ if (rec && typeof rec['points'] === 'number') {
567
+ const points = rec['points'];
568
+ const deductions = typeof rec['deductions'] === 'number' ? rec['deductions'] : 0;
569
+ cats.push({ name, score: points - deductions, maxPoints: 100, findings: [] });
570
+ }
571
+ }
572
+ }
573
+ if (cats.length > 0)
574
+ return cats;
575
+ }
576
+ // Criteria object with nested scores → synthetic categories
577
+ const criteria = this.asRecord(obj['criteria']);
578
+ if (criteria) {
579
+ const cats = [];
580
+ for (const [name, value] of Object.entries(criteria)) {
581
+ const rec = this.asRecord(value);
582
+ if (rec && typeof rec['score'] === 'number') {
583
+ cats.push({
584
+ name,
585
+ score: rec['score'],
586
+ maxPoints: 100,
587
+ findings: this.parseIssues(Array.isArray(rec['issues']) ? rec['issues'] : []).map(issue => ({
588
+ criterion: issue.title,
589
+ pointsEarned: 0,
590
+ pointsPossible: 0,
591
+ issues: [issue],
592
+ })),
593
+ });
594
+ }
595
+ }
596
+ if (cats.length > 0)
597
+ return cats;
598
+ }
599
+ return undefined;
600
+ }
601
+ resolveIssuesFlat(obj, result, report, wrapper) {
602
+ const issues = [];
603
+ // Check multiple issue-like keys across all source objects
604
+ const issueKeys = ['issues', 'recommendations', 'warnings', 'findings'];
605
+ for (const source of [obj, result, report, wrapper]) {
606
+ if (!source)
607
+ continue;
608
+ for (const key of issueKeys) {
609
+ if (Array.isArray(source[key])) {
610
+ issues.push(...this.parseIssues(source[key]));
611
+ if (issues.length > 0)
612
+ return issues;
613
+ }
614
+ // Nested: { issues: { items: [...] } } or { issues: { details: [...] } }
615
+ const nested = this.asRecord(source[key]);
616
+ if (nested) {
617
+ const nestedArray = nested['items'] ?? nested['details'] ?? nested['list'];
618
+ if (Array.isArray(nestedArray)) {
619
+ issues.push(...this.parseIssues(nestedArray));
620
+ if (issues.length > 0)
621
+ return issues;
622
+ }
623
+ }
624
+ }
625
+ }
626
+ // issues_found with warnings/suggestions (gpt-5-mini shape)
627
+ for (const source of [obj, wrapper]) {
628
+ if (!source)
629
+ continue;
630
+ const issuesFound = this.asRecord(source['issues_found']);
631
+ if (issuesFound) {
632
+ if (Array.isArray(issuesFound['critical'])) {
633
+ issues.push(...this.parseIssues(issuesFound['critical']));
634
+ }
635
+ if (Array.isArray(issuesFound['warnings'])) {
636
+ issues.push(...this.parseIssues(issuesFound['warnings']));
637
+ }
638
+ if (Array.isArray(issuesFound['suggestions'])) {
639
+ issues.push(...this.parseIssues(issuesFound['suggestions']));
640
+ }
641
+ }
642
+ }
643
+ return issues;
644
+ }
645
+ normalizeDecision(decision, agentType) {
646
+ // Strip emojis and non-ASCII symbols before processing
647
+ const cleaned = decision.replace(/[\u{1F000}-\u{1FFFF}]|[\u{2600}-\u{27BF}]|[\u{FE00}-\u{FE0F}]|[\u{200D}]/gu, '').trim();
648
+ const upper = cleaned.toUpperCase().trim();
649
+ // Extract first word for labels like "PASS - Ready for next phase"
650
+ const firstWord = upper.split(/[\s\-–—]+/)[0] ?? upper;
651
+ if (agentType === 'validator') {
652
+ if (['PASS', 'PASSED', 'OK', 'SUCCESS'].includes(firstWord))
653
+ return 'PASS';
654
+ if (['WARN', 'WARNING', 'CAUTION'].includes(firstWord))
655
+ return 'WARN';
656
+ if (['FAIL', 'FAILED', 'ERROR', 'REJECT'].includes(firstWord))
657
+ return 'FAIL';
658
+ }
659
+ if (agentType === 'executor') {
660
+ if (['SUCCESS', 'COMPLETE', 'DONE', 'PASS'].includes(firstWord))
661
+ return 'COMPLETE';
662
+ if (['PARTIAL', 'INCOMPLETE'].includes(firstWord))
663
+ return 'PARTIAL';
664
+ if (['FAIL', 'FAILED', 'ERROR'].includes(firstWord))
665
+ return 'FAILED';
666
+ }
667
+ return upper;
668
+ }
669
+ parseCategories(raw) {
670
+ return raw
671
+ .filter((item) => typeof item === 'object' && item !== null)
672
+ .map(item => ({
673
+ name: String(item['name'] ?? item['category'] ?? 'Unknown'),
674
+ score: Number(item['score'] ?? item['points'] ?? 0),
675
+ maxPoints: Number(item['maxPoints'] ?? item['max_points'] ?? item['total'] ?? 100),
676
+ findings: this.parseFindings(Array.isArray(item['findings']) ? item['findings'] : []),
677
+ }));
678
+ }
679
+ parseFindings(raw) {
680
+ return raw
681
+ .filter((item) => typeof item === 'object' && item !== null)
682
+ .map(item => ({
683
+ criterion: String(item['criterion'] ?? item['name'] ?? 'Unknown'),
684
+ pointsEarned: Number(item['pointsEarned'] ?? item['points_earned'] ?? item['score'] ?? 0),
685
+ pointsPossible: Number(item['pointsPossible'] ?? item['points_possible'] ?? item['maxPoints'] ?? 0),
686
+ issues: this.parseIssues(Array.isArray(item['issues']) ? item['issues'] : []),
687
+ }));
688
+ }
689
+ parseIssues(raw) {
690
+ // Flatten grouped issues: [{severity: "CRITICAL", issues: [...]}, ...] → flat array
691
+ const flatItems = [];
692
+ for (const item of raw) {
693
+ if (typeof item !== 'object' || item === null)
694
+ continue;
695
+ const rec = item;
696
+ if (Array.isArray(rec['issues'])) {
697
+ // This is a group — recurse into the nested issues array, inheriting severity
698
+ const groupSeverity = rec['severity'];
699
+ for (const sub of rec['issues']) {
700
+ if (typeof sub === 'object' && sub !== null) {
701
+ const subRec = sub;
702
+ if (groupSeverity && !subRec['severity'])
703
+ subRec['severity'] = groupSeverity;
704
+ flatItems.push(subRec);
705
+ }
706
+ }
707
+ }
708
+ else {
709
+ flatItems.push(rec);
710
+ }
711
+ }
712
+ return flatItems
713
+ .map(item => {
714
+ // Resolve file path and line number from various shapes
715
+ let filePath = item['filePath']
716
+ ?? item['file_path']
717
+ ?? item['file'];
718
+ let lineNumber = typeof item['lineNumber'] === 'number'
719
+ ? item['lineNumber']
720
+ : typeof item['line_number'] === 'number'
721
+ ? item['line_number']
722
+ : typeof item['line'] === 'number'
723
+ ? item['line']
724
+ : typeof item['line_start'] === 'number'
725
+ ? item['line_start']
726
+ : undefined;
727
+ // Handle line as string: "24-50" or "24"
728
+ if (lineNumber === undefined && typeof item['line'] === 'string') {
729
+ const lineMatch = item['line'].match(/^(\d+)/);
730
+ if (lineMatch)
731
+ lineNumber = parseInt(lineMatch[1], 10);
732
+ }
733
+ if (lineNumber === undefined && typeof item['line_number'] === 'string') {
734
+ const lineMatch = item['line_number'].match(/^(\d+)/);
735
+ if (lineMatch)
736
+ lineNumber = parseInt(lineMatch[1], 10);
737
+ }
738
+ if (lineNumber === undefined && typeof item['lineNumber'] === 'string') {
739
+ const lineMatch = item['lineNumber'].match(/^(\d+)/);
740
+ if (lineMatch)
741
+ lineNumber = parseInt(lineMatch[1], 10);
742
+ }
743
+ // Handle combined fields: "file_line" or "location" like "src/foo.ts:42-50"
744
+ for (const combinedKey of ['file_line', 'location']) {
745
+ if (!filePath && typeof item[combinedKey] === 'string') {
746
+ const flMatch = item[combinedKey].match(/^([\w/.@-]+\.\w+):(\d+)/);
747
+ if (flMatch) {
748
+ filePath = flMatch[1];
749
+ lineNumber = lineNumber ?? parseInt(flMatch[2], 10);
750
+ }
751
+ else if (combinedKey === 'file_line') {
752
+ filePath = item[combinedKey];
753
+ }
754
+ }
755
+ }
756
+ // Handle locations array: [{ file: "...", line_start: N }]
757
+ if (!filePath && Array.isArray(item['locations']) && item['locations'].length > 0) {
758
+ const loc = this.asRecord(item['locations'][0]);
759
+ if (loc) {
760
+ filePath = loc['file'] ?? loc['filePath'];
761
+ lineNumber = lineNumber ?? (typeof loc['line_start'] === 'number' ? loc['line_start'] : undefined);
762
+ }
763
+ }
764
+ // Resolve title: prefer explicit title/message, fall back to issue/summary/description/name
765
+ const hasExplicitTitle = item['title'] !== undefined || item['message'] !== undefined
766
+ || item['issue'] !== undefined || item['summary'] !== undefined;
767
+ const title = String(item['title'] ?? item['message'] ?? item['issue'] ?? item['summary']
768
+ ?? item['name'] ?? item['description'] ?? 'Untitled Issue');
769
+ // For description: if title consumed 'description', use explanation/suggestion/recommendation instead
770
+ const detailsStr = typeof item['details'] === 'string' ? item['details'] : undefined;
771
+ const description = hasExplicitTitle
772
+ ? String(item['description'] ?? detailsStr ?? item['explanation'] ?? item['suggestion'] ?? item['recommendation'] ?? '')
773
+ : String(item['explanation'] ?? detailsStr ?? item['suggestion'] ?? item['recommendation'] ?? item['description'] ?? '');
774
+ return {
775
+ title,
776
+ priority: this.normalizePriority(item['priority'] ?? item['type']),
777
+ severity: this.normalizeSeverity(item['severity']),
778
+ failureCode: item['failureCode']
779
+ ?? item['failure_code']
780
+ ?? item['code'],
781
+ filePath,
782
+ lineNumber,
783
+ description,
784
+ };
785
+ });
786
+ }
787
+ parseArtifacts(raw) {
788
+ return raw
789
+ .filter((item) => typeof item === 'object' && item !== null)
790
+ .map(item => ({
791
+ name: String(item['name'] ?? 'Untitled'),
792
+ path: String(item['path'] ?? ''),
793
+ size: typeof item['size'] === 'number' ? item['size'] : undefined,
794
+ contentType: item['contentType'] ?? item['content_type'],
795
+ }));
796
+ }
797
+ normalizePriority(value) {
798
+ const str = String(value ?? 'suggested').toLowerCase();
799
+ if (['critical', 'high', 'p0'].includes(str))
800
+ return 'critical';
801
+ if (['backlog', 'low', 'p2'].includes(str))
802
+ return 'backlog';
803
+ return 'suggested';
804
+ }
805
+ normalizeSeverity(value) {
806
+ const str = String(value ?? 'medium').toLowerCase();
807
+ if (str === 'critical')
808
+ return 'critical';
809
+ if (str === 'high')
810
+ return 'high';
811
+ if (str === 'low')
812
+ return 'low';
813
+ if (['info', 'informational', 'note'].includes(str))
814
+ return 'info';
815
+ return 'medium';
816
+ }
817
+ }
818
+ //# sourceMappingURL=OutputExtractor.js.map