@uluops/core 0.8.2 → 0.10.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.
- package/README.md +84 -25
- package/dist/ai/AIProvider.d.ts.map +1 -1
- package/dist/ai/AIProvider.js +28 -12
- package/dist/ai/AIProvider.js.map +1 -1
- package/dist/ai/ModelCatalog.js +2 -2
- package/dist/ai/ModelCatalog.js.map +1 -1
- package/dist/ai/ToolAdapter.d.ts.map +1 -1
- package/dist/ai/ToolAdapter.js +16 -27
- package/dist/ai/ToolAdapter.js.map +1 -1
- package/dist/ai/{ShellExecutor.d.ts.map → shellExecutor.d.ts.map} +1 -1
- package/dist/ai/{ShellExecutor.js → shellExecutor.js} +3 -2
- package/dist/ai/{ShellExecutor.js.map → shellExecutor.js.map} +1 -1
- package/dist/analysis/AnalysisSummaryExtractor.d.ts +135 -0
- package/dist/analysis/AnalysisSummaryExtractor.d.ts.map +1 -0
- package/dist/analysis/AnalysisSummaryExtractor.js +465 -0
- package/dist/analysis/AnalysisSummaryExtractor.js.map +1 -0
- package/dist/analysis/index.d.ts +2 -0
- package/dist/analysis/index.d.ts.map +1 -0
- package/dist/analysis/index.js +2 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/client/UluOpsClient.d.ts +12 -9
- package/dist/client/UluOpsClient.d.ts.map +1 -1
- package/dist/client/UluOpsClient.js +76 -47
- package/dist/client/UluOpsClient.js.map +1 -1
- package/dist/constants.d.ts +4 -3
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +13 -3
- package/dist/constants.js.map +1 -1
- package/dist/errors/index.d.ts +38 -8
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +52 -7
- package/dist/errors/index.js.map +1 -1
- package/dist/executor/AgentExecutor.d.ts +2 -0
- package/dist/executor/AgentExecutor.d.ts.map +1 -1
- package/dist/executor/AgentExecutor.js +33 -16
- package/dist/executor/AgentExecutor.js.map +1 -1
- package/dist/executor/CommandExecutor.d.ts +3 -1
- package/dist/executor/CommandExecutor.d.ts.map +1 -1
- package/dist/executor/CommandExecutor.js +17 -39
- package/dist/executor/CommandExecutor.js.map +1 -1
- package/dist/executor/PipelineExecutor.d.ts +12 -4
- package/dist/executor/PipelineExecutor.d.ts.map +1 -1
- package/dist/executor/PipelineExecutor.js +127 -23
- package/dist/executor/PipelineExecutor.js.map +1 -1
- package/dist/executor/ToolHandler.d.ts +5 -0
- package/dist/executor/ToolHandler.d.ts.map +1 -1
- package/dist/executor/ToolHandler.js +23 -14
- package/dist/executor/ToolHandler.js.map +1 -1
- package/dist/executor/WorkflowExecutor.d.ts +18 -2
- package/dist/executor/WorkflowExecutor.d.ts.map +1 -1
- package/dist/executor/WorkflowExecutor.js +107 -37
- package/dist/executor/WorkflowExecutor.js.map +1 -1
- package/dist/executor/classifyDecision.d.ts +2 -1
- package/dist/executor/classifyDecision.d.ts.map +1 -1
- package/dist/executor/classifyDecision.js +4 -2
- package/dist/executor/classifyDecision.js.map +1 -1
- package/dist/executor/mapCategory.d.ts +5 -0
- package/dist/executor/mapCategory.d.ts.map +1 -0
- package/dist/executor/mapCategory.js +5 -0
- package/dist/executor/mapCategory.js.map +1 -0
- package/dist/executor/preflight.d.ts.map +1 -1
- package/dist/executor/preflight.js +53 -12
- package/dist/executor/preflight.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/parser/OutputExtractor.d.ts +7 -25
- package/dist/parser/OutputExtractor.d.ts.map +1 -1
- package/dist/parser/OutputExtractor.js +23 -528
- package/dist/parser/OutputExtractor.js.map +1 -1
- package/dist/parser/OutputNormalizer.d.ts +45 -0
- package/dist/parser/OutputNormalizer.d.ts.map +1 -0
- package/dist/parser/OutputNormalizer.js +572 -0
- package/dist/parser/OutputNormalizer.js.map +1 -0
- package/dist/parser/outputSchemas.d.ts +266 -36
- package/dist/parser/outputSchemas.d.ts.map +1 -1
- package/dist/parser/outputSchemas.js +109 -0
- package/dist/parser/outputSchemas.js.map +1 -1
- package/dist/registry/RegistryClient.d.ts +9 -7
- package/dist/registry/RegistryClient.d.ts.map +1 -1
- package/dist/registry/RegistryClient.js +82 -51
- package/dist/registry/RegistryClient.js.map +1 -1
- package/dist/submission/SubmissionClient.d.ts +81 -0
- package/dist/submission/SubmissionClient.d.ts.map +1 -0
- package/dist/submission/SubmissionClient.js +266 -0
- package/dist/submission/SubmissionClient.js.map +1 -0
- package/dist/types/agent.d.ts +13 -1
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/command.d.ts +1 -1
- package/dist/types/command.d.ts.map +1 -1
- package/dist/types/config.d.ts +21 -7
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/execution.d.ts +7 -0
- package/dist/types/execution.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/pipeline.d.ts +18 -6
- package/dist/types/pipeline.d.ts.map +1 -1
- package/dist/types/registry.d.ts +11 -4
- package/dist/types/registry.d.ts.map +1 -1
- package/dist/types/submission.d.ts +116 -0
- package/dist/types/submission.d.ts.map +1 -0
- package/dist/types/submission.js +2 -0
- package/dist/types/submission.js.map +1 -0
- package/dist/types/validation.d.ts +4 -0
- package/dist/types/validation.d.ts.map +1 -1
- package/dist/types/workflow.d.ts +7 -1
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/utils/aggregateScores.d.ts +26 -0
- package/dist/utils/aggregateScores.d.ts.map +1 -0
- package/dist/utils/aggregateScores.js +45 -0
- package/dist/utils/aggregateScores.js.map +1 -0
- package/dist/utils/parseRef.d.ts.map +1 -1
- package/dist/utils/parseRef.js +3 -1
- package/dist/utils/parseRef.js.map +1 -1
- package/dist/utils/topoSort.d.ts.map +1 -1
- package/dist/utils/topoSort.js +5 -2
- package/dist/utils/topoSort.js.map +1 -1
- package/dist/validation/ValidationClient.d.ts +30 -0
- package/dist/validation/ValidationClient.d.ts.map +1 -1
- package/dist/validation/ValidationClient.js +106 -18
- package/dist/validation/ValidationClient.js.map +1 -1
- package/package.json +6 -6
- package/dist/ai/index.d.ts +0 -6
- package/dist/ai/index.d.ts.map +0 -1
- package/dist/ai/index.js +0 -4
- package/dist/ai/index.js.map +0 -1
- package/dist/registry/index.d.ts +0 -2
- package/dist/registry/index.d.ts.map +0 -1
- package/dist/registry/index.js +0 -2
- package/dist/registry/index.js.map +0 -1
- package/dist/validation/index.d.ts +0 -2
- package/dist/validation/index.d.ts.map +0 -1
- package/dist/validation/index.js +0 -2
- package/dist/validation/index.js.map +0 -1
- /package/dist/ai/{ShellExecutor.d.ts → shellExecutor.d.ts} +0 -0
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import type { AgentType } from '../types/execution.js';
|
|
2
2
|
import type { ParsedOutput, ExtractionOptions, ExtractionResult } from '../types/parser.js';
|
|
3
3
|
/**
|
|
4
|
-
* Extracts structured output from LLM responses using a
|
|
5
|
-
*
|
|
4
|
+
* Extracts structured output from LLM responses using a 4-strategy fallback:
|
|
5
|
+
* 0. AI SDK structured output (highest confidence — schema-validated by the SDK)
|
|
6
|
+
* 1. JSON code fence
|
|
6
7
|
* 2. Inline JSON detection
|
|
7
8
|
* 3. Structured text pattern matching (lowest confidence)
|
|
9
|
+
*
|
|
10
|
+
* Normalization of parsed JSON into the unified ParsedOutput type is delegated
|
|
11
|
+
* to OutputNormalizer, which handles the diversity of LLM output shapes.
|
|
8
12
|
*/
|
|
9
13
|
export declare class OutputExtractor {
|
|
14
|
+
private normalizer;
|
|
10
15
|
private static readonly INLINE_JSON_PATTERN;
|
|
11
16
|
private static readonly STRUCTURED_PATTERNS;
|
|
12
17
|
/**
|
|
@@ -25,28 +30,5 @@ export declare class OutputExtractor {
|
|
|
25
30
|
private extractIssuesFromText;
|
|
26
31
|
private inferPriorityFromContext;
|
|
27
32
|
private inferSeverityFromContext;
|
|
28
|
-
/** Resolved source objects from common nesting patterns. Reduces parameter passing across resolve methods. */
|
|
29
|
-
private buildParseSources;
|
|
30
|
-
private normalizeOutput;
|
|
31
|
-
private resolveAgentFields;
|
|
32
|
-
private attachFlatIssues;
|
|
33
|
-
private asRecord;
|
|
34
|
-
/**
|
|
35
|
-
* Scan all top-level object values for one that contains 'score' or 'decision'.
|
|
36
|
-
* Handles arbitrary wrapper names (validation, validations, validationResults, etc.)
|
|
37
|
-
* without whitelisting specific field names.
|
|
38
|
-
*/
|
|
39
|
-
private findWrapperWithScoreOrDecision;
|
|
40
|
-
private resolveDecisionField;
|
|
41
|
-
private resolveScoreField;
|
|
42
|
-
private resolveCategories;
|
|
43
|
-
private resolveIssuesFlat;
|
|
44
|
-
private normalizeDecision;
|
|
45
|
-
private parseCategories;
|
|
46
|
-
private parseFindings;
|
|
47
|
-
private parseIssues;
|
|
48
|
-
private parseArtifacts;
|
|
49
|
-
private normalizePriority;
|
|
50
|
-
private normalizeSeverity;
|
|
51
33
|
}
|
|
52
34
|
//# sourceMappingURL=OutputExtractor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OutputExtractor.d.ts","sourceRoot":"","sources":["../../src/parser/OutputExtractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,KAAK,EACV,YAAY,
|
|
1
|
+
{"version":3,"file":"OutputExtractor.d.ts","sourceRoot":"","sources":["../../src/parser/OutputExtractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,oBAAoB,CAAC;AAI5B;;;;;;;;;GASG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,UAAU,CAA0B;IAE5C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAyD;IACpG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAazC;IAEF;;OAEG;IACH,OAAO,CACL,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE,iBAAsB,GAC9B,YAAY;IAKf;;OAEG;IACH,mBAAmB,CACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB;IAgFnB,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,iBAAiB;IAoDzB,OAAO,CAAC,mBAAmB;IAqC3B,OAAO,CAAC,yBAAyB;IA0DjC,OAAO,CAAC,qBAAqB;IAwB7B,OAAO,CAAC,wBAAwB;IAQhC,OAAO,CAAC,wBAAwB;CAOjC"}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { ParseError } from '../errors/index.js';
|
|
2
|
+
import { OutputNormalizer } from './OutputNormalizer.js';
|
|
2
3
|
/**
|
|
3
|
-
* Extracts structured output from LLM responses using a
|
|
4
|
-
*
|
|
4
|
+
* Extracts structured output from LLM responses using a 4-strategy fallback:
|
|
5
|
+
* 0. AI SDK structured output (highest confidence — schema-validated by the SDK)
|
|
6
|
+
* 1. JSON code fence
|
|
5
7
|
* 2. Inline JSON detection
|
|
6
8
|
* 3. Structured text pattern matching (lowest confidence)
|
|
9
|
+
*
|
|
10
|
+
* Normalization of parsed JSON into the unified ParsedOutput type is delegated
|
|
11
|
+
* to OutputNormalizer, which handles the diversity of LLM output shapes.
|
|
7
12
|
*/
|
|
8
13
|
export class OutputExtractor {
|
|
14
|
+
normalizer = new OutputNormalizer();
|
|
9
15
|
static INLINE_JSON_PATTERN = /\{[\s\S]*?(?:"decision"|"status"|"score")[\s\S]*?\}/;
|
|
10
16
|
static STRUCTURED_PATTERNS = {
|
|
11
17
|
decision: /(?:decision|status|result)\s*[:=]\s*["']?(\w+)["']?/i,
|
|
@@ -33,11 +39,22 @@ export class OutputExtractor {
|
|
|
33
39
|
*/
|
|
34
40
|
extractWithMetadata(content, agentType, options = {}) {
|
|
35
41
|
const warnings = [];
|
|
42
|
+
// ── Extraction strategies (ordered by confidence) ──────────────────────
|
|
43
|
+
// Confidence values are heuristics encoding relative trust in each method.
|
|
44
|
+
// They were calibrated against Claude's output patterns and may need
|
|
45
|
+
// recalibration as new models (GPT, Gemini) are added. The 0.7 threshold
|
|
46
|
+
// in AgentExecutor gates EXTRACTION_FAILED decisions — any strategy below
|
|
47
|
+
// that threshold produces results that won't be trusted as real decisions.
|
|
48
|
+
//
|
|
49
|
+
// Strategy 1: JSON code fence (0.95) — model explicitly wrapped output
|
|
50
|
+
// Strategy 2: Whole/inline JSON (0.9/0.75) — found parseable JSON in text
|
|
51
|
+
// Strategy 3: Structured text (0.5) — regex matched decision/score patterns
|
|
52
|
+
// Strategy 4: Fallback (0.0) — nothing found, emit ERROR/0 defaults
|
|
36
53
|
// Strategy 1: Try JSON code fence (highest confidence)
|
|
37
54
|
const fenceResult = this.extractFromCodeFence(content, options);
|
|
38
55
|
if (fenceResult) {
|
|
39
56
|
return {
|
|
40
|
-
output: this.normalizeOutput(fenceResult, agentType),
|
|
57
|
+
output: this.normalizer.normalizeOutput(fenceResult, agentType),
|
|
41
58
|
method: 'json_code_fence',
|
|
42
59
|
confidence: 0.95,
|
|
43
60
|
warnings,
|
|
@@ -47,7 +64,7 @@ export class OutputExtractor {
|
|
|
47
64
|
const wholeJsonResult = this.extractWholeJson(content);
|
|
48
65
|
if (wholeJsonResult) {
|
|
49
66
|
return {
|
|
50
|
-
output: this.normalizeOutput(wholeJsonResult, agentType),
|
|
67
|
+
output: this.normalizer.normalizeOutput(wholeJsonResult, agentType),
|
|
51
68
|
method: 'inline_json',
|
|
52
69
|
confidence: 0.9,
|
|
53
70
|
warnings,
|
|
@@ -58,7 +75,7 @@ export class OutputExtractor {
|
|
|
58
75
|
if (inlineResult) {
|
|
59
76
|
warnings.push('Extracted from inline JSON - consider using code fence for reliability');
|
|
60
77
|
return {
|
|
61
|
-
output: this.normalizeOutput(inlineResult, agentType),
|
|
78
|
+
output: this.normalizer.normalizeOutput(inlineResult, agentType),
|
|
62
79
|
method: 'inline_json',
|
|
63
80
|
confidence: 0.75,
|
|
64
81
|
warnings,
|
|
@@ -215,7 +232,7 @@ export class OutputExtractor {
|
|
|
215
232
|
}
|
|
216
233
|
const output = {
|
|
217
234
|
decision: decisionMatch
|
|
218
|
-
? this.normalizeDecision(decisionMatch[1] ?? '', agentType)
|
|
235
|
+
? this.normalizer.normalizeDecision(decisionMatch[1] ?? '', agentType)
|
|
219
236
|
: 'UNKNOWN',
|
|
220
237
|
};
|
|
221
238
|
if (scoreMatch?.[1]) {
|
|
@@ -291,527 +308,5 @@ export class OutputExtractor {
|
|
|
291
308
|
return 'low';
|
|
292
309
|
return 'medium';
|
|
293
310
|
}
|
|
294
|
-
/** Resolved source objects from common nesting patterns. Reduces parameter passing across resolve methods. */
|
|
295
|
-
buildParseSources(obj) {
|
|
296
|
-
const result = this.asRecord(obj['result']);
|
|
297
|
-
const summary = this.asRecord(obj['summary']) ?? this.asRecord(result?.['summary']);
|
|
298
|
-
const report = this.asRecord(obj['report']);
|
|
299
|
-
const reportResults = this.asRecord(report?.['results']) ?? this.asRecord(obj['results']);
|
|
300
|
-
const reportSummary = this.asRecord(report?.['summary']) ?? this.asRecord(reportResults?.['summary']);
|
|
301
|
-
const validationSummary = this.findWrapperWithScoreOrDecision(obj);
|
|
302
|
-
return { obj, result, summary, report, reportResults, reportSummary, validationSummary };
|
|
303
|
-
}
|
|
304
|
-
normalizeOutput(raw, agentType) {
|
|
305
|
-
if (!raw || typeof raw !== 'object') {
|
|
306
|
-
return { decision: 'ERROR' };
|
|
307
|
-
}
|
|
308
|
-
const obj = raw;
|
|
309
|
-
const sources = this.buildParseSources(obj);
|
|
310
|
-
const output = {
|
|
311
|
-
decision: this.normalizeDecision(this.resolveDecisionField(sources), agentType),
|
|
312
|
-
rawJson: raw,
|
|
313
|
-
};
|
|
314
|
-
// Resolve score
|
|
315
|
-
const rawScore = this.resolveScoreField(sources);
|
|
316
|
-
if (typeof rawScore === 'number') {
|
|
317
|
-
output.score = rawScore;
|
|
318
|
-
}
|
|
319
|
-
else if (typeof rawScore === 'string') {
|
|
320
|
-
output.score = parseFloat(rawScore);
|
|
321
|
-
}
|
|
322
|
-
this.resolveAgentFields(output, sources);
|
|
323
|
-
if (Array.isArray(obj['artifacts'])) {
|
|
324
|
-
output.artifacts = this.parseArtifacts(obj['artifacts']);
|
|
325
|
-
}
|
|
326
|
-
return output;
|
|
327
|
-
}
|
|
328
|
-
resolveAgentFields(output, sources) {
|
|
329
|
-
const { obj, result, summary, report } = sources;
|
|
330
|
-
// Resolve maxScore
|
|
331
|
-
const rawMaxScore = obj['maxScore'] ?? obj['max_score']
|
|
332
|
-
?? result?.['max_score'] ?? result?.['maxScore']
|
|
333
|
-
?? summary?.['max_score'] ?? summary?.['maxScore']
|
|
334
|
-
?? obj['pass_threshold'];
|
|
335
|
-
if (typeof rawMaxScore === 'number') {
|
|
336
|
-
output.maxScore = rawMaxScore;
|
|
337
|
-
}
|
|
338
|
-
else if (typeof rawMaxScore === 'string') {
|
|
339
|
-
output.maxScore = parseInt(rawMaxScore, 10);
|
|
340
|
-
}
|
|
341
|
-
// Resolve categories
|
|
342
|
-
output.categories = this.resolveCategories(obj, result, report);
|
|
343
|
-
// If no score found but categories exist, sum category scores
|
|
344
|
-
if (output.score === undefined && output.categories && output.categories.length > 0) {
|
|
345
|
-
output.score = output.categories.reduce((sum, c) => sum + c.score, 0);
|
|
346
|
-
}
|
|
347
|
-
// Resolve flat issues and attach to categories
|
|
348
|
-
this.attachFlatIssues(output, sources);
|
|
349
|
-
}
|
|
350
|
-
attachFlatIssues(output, sources) {
|
|
351
|
-
const flatIssues = this.resolveIssuesFlat(sources.obj, sources.result, sources.report, sources.validationSummary);
|
|
352
|
-
if (flatIssues.length === 0)
|
|
353
|
-
return;
|
|
354
|
-
const issuesFinding = {
|
|
355
|
-
criterion: 'Extracted findings',
|
|
356
|
-
pointsEarned: 0,
|
|
357
|
-
pointsPossible: 0,
|
|
358
|
-
issues: flatIssues,
|
|
359
|
-
};
|
|
360
|
-
if (!output.categories || output.categories.length === 0) {
|
|
361
|
-
output.categories = [{
|
|
362
|
-
name: 'Extracted Issues',
|
|
363
|
-
score: output.score ?? 0,
|
|
364
|
-
maxScore: output.maxScore ?? 100,
|
|
365
|
-
findings: [issuesFinding],
|
|
366
|
-
}];
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
369
|
-
const emptyCategory = output.categories.find(c => c.findings.length === 0);
|
|
370
|
-
if (emptyCategory) {
|
|
371
|
-
emptyCategory.findings.push(issuesFinding);
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
output.categories.push({
|
|
375
|
-
name: 'Extracted Issues',
|
|
376
|
-
score: output.score ?? 0,
|
|
377
|
-
maxScore: output.maxScore ?? 100,
|
|
378
|
-
findings: [issuesFinding],
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
asRecord(value) {
|
|
384
|
-
return (value && typeof value === 'object' && !Array.isArray(value))
|
|
385
|
-
? value
|
|
386
|
-
: undefined;
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* Scan all top-level object values for one that contains 'score' or 'decision'.
|
|
390
|
-
* Handles arbitrary wrapper names (validation, validations, validationResults, etc.)
|
|
391
|
-
* without whitelisting specific field names.
|
|
392
|
-
*/
|
|
393
|
-
findWrapperWithScoreOrDecision(obj) {
|
|
394
|
-
// Skip known non-wrapper fields
|
|
395
|
-
const skip = new Set(['issues', 'categories', 'recommendations', 'evidence',
|
|
396
|
-
'reasoning', 'reasoning_trace', 'notes', 'auto_fail_conditions', 'filesReviewed',
|
|
397
|
-
'files_reviewed', 'artifacts', 'result', 'summary', 'report']);
|
|
398
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
399
|
-
if (skip.has(key))
|
|
400
|
-
continue;
|
|
401
|
-
const rec = this.asRecord(value);
|
|
402
|
-
if (!rec)
|
|
403
|
-
continue;
|
|
404
|
-
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) {
|
|
405
|
-
return rec;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
return undefined;
|
|
409
|
-
}
|
|
410
|
-
resolveDecisionField(ctx) {
|
|
411
|
-
const { obj } = ctx;
|
|
412
|
-
const sources = [ctx.obj, ctx.summary, ctx.result, ctx.report, ctx.reportResults, ctx.reportSummary, ctx.validationSummary];
|
|
413
|
-
// Check each source for decision/final_decision fields
|
|
414
|
-
for (const source of sources) {
|
|
415
|
-
if (!source)
|
|
416
|
-
continue;
|
|
417
|
-
for (const key of ['decision', 'final_decision']) {
|
|
418
|
-
const d = source[key];
|
|
419
|
-
if (typeof d === 'string')
|
|
420
|
-
return d;
|
|
421
|
-
// Handle decision as object: { pass: true, label: "PASS" } or { result: "PASS" }
|
|
422
|
-
if (d && typeof d === 'object') {
|
|
423
|
-
const dObj = d;
|
|
424
|
-
if (typeof dObj['result'] === 'string')
|
|
425
|
-
return dObj['result'];
|
|
426
|
-
if (typeof dObj['label'] === 'string')
|
|
427
|
-
return dObj['label'];
|
|
428
|
-
if (typeof dObj['value'] === 'string')
|
|
429
|
-
return dObj['value'];
|
|
430
|
-
if (typeof dObj['status'] === 'string')
|
|
431
|
-
return dObj['status'];
|
|
432
|
-
if (typeof dObj['pass'] === 'boolean')
|
|
433
|
-
return dObj['pass'] ? 'PASS' : 'FAIL';
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
// Fallback to status field
|
|
438
|
-
for (const source of sources) {
|
|
439
|
-
if (!source)
|
|
440
|
-
continue;
|
|
441
|
-
if (typeof source['status'] === 'string')
|
|
442
|
-
return source['status'];
|
|
443
|
-
}
|
|
444
|
-
// Fallback: check if summary is a string starting with a decision word
|
|
445
|
-
if (typeof obj['summary'] === 'string') {
|
|
446
|
-
const summaryFirst = obj['summary'].split(/[\s\-–—]+/)[0]?.toUpperCase();
|
|
447
|
-
if (summaryFirst && ['PASS', 'FAIL', 'WARN', 'ERROR', 'COMPLETE'].includes(summaryFirst)) {
|
|
448
|
-
return summaryFirst;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
return 'UNKNOWN';
|
|
452
|
-
}
|
|
453
|
-
resolveScoreField(ctx) {
|
|
454
|
-
const { obj } = ctx;
|
|
455
|
-
const sources = [ctx.obj, ctx.summary, ctx.result, ctx.report, ctx.reportResults, ctx.reportSummary, ctx.validationSummary];
|
|
456
|
-
// Check each source for a score value
|
|
457
|
-
for (const source of sources) {
|
|
458
|
-
if (!source)
|
|
459
|
-
continue;
|
|
460
|
-
for (const scoreKey of ['score', 'total_score', 'score_total']) {
|
|
461
|
-
const s = source[scoreKey];
|
|
462
|
-
if (typeof s === 'number')
|
|
463
|
-
return s;
|
|
464
|
-
if (typeof s === 'string' && s.trim() !== '' && !isNaN(Number(s)))
|
|
465
|
-
return s;
|
|
466
|
-
// Handle score as object: { total: 85, ... }
|
|
467
|
-
if (s && typeof s === 'object') {
|
|
468
|
-
const sObj = s;
|
|
469
|
-
for (const key of ['total', 'value', 'overall', 'final']) {
|
|
470
|
-
if (typeof sObj[key] === 'number')
|
|
471
|
-
return sObj[key];
|
|
472
|
-
if (typeof sObj[key] === 'string')
|
|
473
|
-
return sObj[key];
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
// Note: validationSummary (from findWrapperWithScoreOrDecision) is already in the sources loop above.
|
|
479
|
-
// Check scores object with named sub-scores (gpt-4.1-nano shape: { scores: { "Code Quality": 23, ... } })
|
|
480
|
-
const scores = this.asRecord(obj['scores']);
|
|
481
|
-
if (scores) {
|
|
482
|
-
if (typeof scores['Total'] === 'number')
|
|
483
|
-
return scores['Total'];
|
|
484
|
-
if (typeof scores['total'] === 'number')
|
|
485
|
-
return scores['total'];
|
|
486
|
-
}
|
|
487
|
-
// Check breakdown with sub-scores sum (search wrapper objects too)
|
|
488
|
-
const wrapper = this.findWrapperWithScoreOrDecision(obj);
|
|
489
|
-
const breakdown = this.asRecord(obj['breakdown'])
|
|
490
|
-
?? this.asRecord(obj['score_breakdown'])
|
|
491
|
-
?? this.asRecord(wrapper?.['breakdown'])
|
|
492
|
-
?? this.asRecord(wrapper?.['score_breakdown']);
|
|
493
|
-
if (breakdown) {
|
|
494
|
-
const values = [];
|
|
495
|
-
for (const v of Object.values(breakdown)) {
|
|
496
|
-
if (typeof v === 'number') {
|
|
497
|
-
values.push(v);
|
|
498
|
-
continue;
|
|
499
|
-
}
|
|
500
|
-
// Handle { points: N, deductions: N } shape
|
|
501
|
-
const rec = this.asRecord(v);
|
|
502
|
-
if (rec && typeof rec['points'] === 'number') {
|
|
503
|
-
const deductions = typeof rec['deductions'] === 'number' ? rec['deductions'] : 0;
|
|
504
|
-
values.push(rec['points'] - deductions);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
if (values.length > 0)
|
|
508
|
-
return values.reduce((a, b) => a + b, 0);
|
|
509
|
-
}
|
|
510
|
-
// Check criteria with sub-scores sum (gpt-4.1-nano shape)
|
|
511
|
-
const criteria = this.asRecord(obj['criteria']);
|
|
512
|
-
if (criteria) {
|
|
513
|
-
const values = [];
|
|
514
|
-
for (const v of Object.values(criteria)) {
|
|
515
|
-
if (typeof v === 'number') {
|
|
516
|
-
values.push(v);
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
519
|
-
const rec = this.asRecord(v);
|
|
520
|
-
if (rec && typeof rec['score'] === 'number')
|
|
521
|
-
values.push(rec['score']);
|
|
522
|
-
}
|
|
523
|
-
if (values.length > 0)
|
|
524
|
-
return values.reduce((a, b) => a + b, 0);
|
|
525
|
-
}
|
|
526
|
-
return undefined;
|
|
527
|
-
}
|
|
528
|
-
resolveCategories(obj, result, report) {
|
|
529
|
-
// Direct categories array
|
|
530
|
-
for (const source of [obj, result, report]) {
|
|
531
|
-
if (!source)
|
|
532
|
-
continue;
|
|
533
|
-
if (Array.isArray(source['categories'])) {
|
|
534
|
-
return this.parseCategories(source['categories']);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
// Named scores object → synthetic categories (e.g., { scores: { "Code Quality": 23, ... } })
|
|
538
|
-
const scores = this.asRecord(obj['scores']) ?? this.asRecord(report?.['scores']);
|
|
539
|
-
if (scores) {
|
|
540
|
-
const cats = [];
|
|
541
|
-
for (const [name, value] of Object.entries(scores)) {
|
|
542
|
-
if (typeof value === 'number' && name !== 'Total' && name !== 'total' && name !== 'pass_threshold') {
|
|
543
|
-
cats.push({ name, score: value, maxScore: 100, findings: [] });
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
if (cats.length > 0)
|
|
547
|
-
return cats;
|
|
548
|
-
}
|
|
549
|
-
// Breakdown object → synthetic categories
|
|
550
|
-
// Search top-level breakdown, score_breakdown, and inside any wrapper object
|
|
551
|
-
const wrapper = this.findWrapperWithScoreOrDecision(obj);
|
|
552
|
-
const breakdown = this.asRecord(obj['breakdown'])
|
|
553
|
-
?? this.asRecord(obj['score_breakdown'])
|
|
554
|
-
?? this.asRecord(wrapper?.['breakdown'])
|
|
555
|
-
?? this.asRecord(wrapper?.['score_breakdown']);
|
|
556
|
-
if (breakdown) {
|
|
557
|
-
const cats = [];
|
|
558
|
-
for (const [name, value] of Object.entries(breakdown)) {
|
|
559
|
-
if (typeof value === 'number') {
|
|
560
|
-
cats.push({ name, score: value, maxScore: 100, findings: [] });
|
|
561
|
-
}
|
|
562
|
-
else {
|
|
563
|
-
// Handle { points: N, deductions: N } shape
|
|
564
|
-
const rec = this.asRecord(value);
|
|
565
|
-
if (rec && typeof rec['points'] === 'number') {
|
|
566
|
-
const points = rec['points'];
|
|
567
|
-
const deductions = typeof rec['deductions'] === 'number' ? rec['deductions'] : 0;
|
|
568
|
-
cats.push({ name, score: points - deductions, maxScore: 100, findings: [] });
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
if (cats.length > 0)
|
|
573
|
-
return cats;
|
|
574
|
-
}
|
|
575
|
-
// Criteria object with nested scores → synthetic categories
|
|
576
|
-
const criteria = this.asRecord(obj['criteria']);
|
|
577
|
-
if (criteria) {
|
|
578
|
-
const cats = [];
|
|
579
|
-
for (const [name, value] of Object.entries(criteria)) {
|
|
580
|
-
const rec = this.asRecord(value);
|
|
581
|
-
if (rec && typeof rec['score'] === 'number') {
|
|
582
|
-
cats.push({
|
|
583
|
-
name,
|
|
584
|
-
score: rec['score'],
|
|
585
|
-
maxScore: 100,
|
|
586
|
-
findings: this.parseIssues(Array.isArray(rec['issues']) ? rec['issues'] : []).map(issue => ({
|
|
587
|
-
criterion: issue.title,
|
|
588
|
-
pointsEarned: 0,
|
|
589
|
-
pointsPossible: 0,
|
|
590
|
-
issues: [issue],
|
|
591
|
-
})),
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
if (cats.length > 0)
|
|
596
|
-
return cats;
|
|
597
|
-
}
|
|
598
|
-
return undefined;
|
|
599
|
-
}
|
|
600
|
-
resolveIssuesFlat(obj, result, report, wrapper) {
|
|
601
|
-
const issues = [];
|
|
602
|
-
// Check multiple issue-like keys across all source objects
|
|
603
|
-
const issueKeys = ['issues', 'recommendations', 'warnings', 'findings'];
|
|
604
|
-
for (const source of [obj, result, report, wrapper]) {
|
|
605
|
-
if (!source)
|
|
606
|
-
continue;
|
|
607
|
-
for (const key of issueKeys) {
|
|
608
|
-
if (Array.isArray(source[key])) {
|
|
609
|
-
issues.push(...this.parseIssues(source[key]));
|
|
610
|
-
if (issues.length > 0)
|
|
611
|
-
return issues;
|
|
612
|
-
}
|
|
613
|
-
// Nested: { issues: { items: [...] } } or { issues: { details: [...] } }
|
|
614
|
-
const nested = this.asRecord(source[key]);
|
|
615
|
-
if (nested) {
|
|
616
|
-
const nestedArray = nested['items'] ?? nested['details'] ?? nested['list'];
|
|
617
|
-
if (Array.isArray(nestedArray)) {
|
|
618
|
-
issues.push(...this.parseIssues(nestedArray));
|
|
619
|
-
if (issues.length > 0)
|
|
620
|
-
return issues;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
// issues_found with warnings/suggestions (gpt-5-mini shape)
|
|
626
|
-
for (const source of [obj, wrapper]) {
|
|
627
|
-
if (!source)
|
|
628
|
-
continue;
|
|
629
|
-
const issuesFound = this.asRecord(source['issues_found']);
|
|
630
|
-
if (issuesFound) {
|
|
631
|
-
if (Array.isArray(issuesFound['critical'])) {
|
|
632
|
-
issues.push(...this.parseIssues(issuesFound['critical']));
|
|
633
|
-
}
|
|
634
|
-
if (Array.isArray(issuesFound['warnings'])) {
|
|
635
|
-
issues.push(...this.parseIssues(issuesFound['warnings']));
|
|
636
|
-
}
|
|
637
|
-
if (Array.isArray(issuesFound['suggestions'])) {
|
|
638
|
-
issues.push(...this.parseIssues(issuesFound['suggestions']));
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
return issues;
|
|
643
|
-
}
|
|
644
|
-
normalizeDecision(decision, agentType) {
|
|
645
|
-
// Strip emojis and non-ASCII symbols before processing
|
|
646
|
-
const cleaned = decision.replace(/[\u{1F000}-\u{1FFFF}]|[\u{2600}-\u{27BF}]|[\u{FE00}-\u{FE0F}]|[\u{200D}]/gu, '').trim();
|
|
647
|
-
const upper = cleaned.toUpperCase().trim();
|
|
648
|
-
// Extract first word for labels like "PASS - Ready for next phase"
|
|
649
|
-
const firstWord = upper.split(/[\s\-–—]+/)[0] ?? upper;
|
|
650
|
-
if (agentType === 'validator') {
|
|
651
|
-
if (['PASS', 'PASSED', 'OK', 'SUCCESS'].includes(firstWord))
|
|
652
|
-
return 'PASS';
|
|
653
|
-
if (['WARN', 'WARNING', 'CAUTION'].includes(firstWord))
|
|
654
|
-
return 'WARN';
|
|
655
|
-
if (['FAIL', 'FAILED', 'ERROR', 'REJECT'].includes(firstWord))
|
|
656
|
-
return 'FAIL';
|
|
657
|
-
}
|
|
658
|
-
if (agentType === 'executor') {
|
|
659
|
-
if (['SUCCESS', 'COMPLETE', 'DONE', 'PASS'].includes(firstWord))
|
|
660
|
-
return 'COMPLETE';
|
|
661
|
-
if (['PARTIAL', 'INCOMPLETE'].includes(firstWord))
|
|
662
|
-
return 'PARTIAL';
|
|
663
|
-
if (['FAIL', 'FAILED', 'ERROR'].includes(firstWord))
|
|
664
|
-
return 'FAILED';
|
|
665
|
-
}
|
|
666
|
-
return upper;
|
|
667
|
-
}
|
|
668
|
-
parseCategories(raw) {
|
|
669
|
-
return raw
|
|
670
|
-
.filter((item) => typeof item === 'object' && item !== null)
|
|
671
|
-
.map(item => ({
|
|
672
|
-
name: String(item['name'] ?? item['category'] ?? 'Unknown'),
|
|
673
|
-
score: Number(item['score'] ?? item['points'] ?? 0),
|
|
674
|
-
maxScore: Number(item['maxScore'] ?? item['maxPoints'] ?? item['max_points'] ?? item['total'] ?? 100),
|
|
675
|
-
findings: this.parseFindings(Array.isArray(item['findings']) ? item['findings'] : []),
|
|
676
|
-
}));
|
|
677
|
-
}
|
|
678
|
-
parseFindings(raw) {
|
|
679
|
-
return raw
|
|
680
|
-
.filter((item) => typeof item === 'object' && item !== null)
|
|
681
|
-
.map(item => ({
|
|
682
|
-
criterion: String(item['criterion'] ?? item['name'] ?? 'Unknown'),
|
|
683
|
-
pointsEarned: Number(item['pointsEarned'] ?? item['points_earned'] ?? item['score'] ?? 0),
|
|
684
|
-
pointsPossible: Number(item['pointsPossible'] ?? item['points_possible'] ?? item['maxScore'] ?? item['maxPoints'] ?? 0),
|
|
685
|
-
issues: this.parseIssues(Array.isArray(item['issues']) ? item['issues'] : []),
|
|
686
|
-
}));
|
|
687
|
-
}
|
|
688
|
-
parseIssues(raw) {
|
|
689
|
-
// Flatten grouped issues: [{severity: "CRITICAL", issues: [...]}, ...] → flat array
|
|
690
|
-
const flatItems = [];
|
|
691
|
-
for (const item of raw) {
|
|
692
|
-
if (typeof item !== 'object' || item === null)
|
|
693
|
-
continue;
|
|
694
|
-
const rec = item;
|
|
695
|
-
if (Array.isArray(rec['issues'])) {
|
|
696
|
-
// This is a group — recurse into the nested issues array, inheriting severity
|
|
697
|
-
const groupSeverity = rec['severity'];
|
|
698
|
-
for (const sub of rec['issues']) {
|
|
699
|
-
if (typeof sub === 'object' && sub !== null) {
|
|
700
|
-
const subRec = sub;
|
|
701
|
-
if (groupSeverity && !subRec['severity'])
|
|
702
|
-
subRec['severity'] = groupSeverity;
|
|
703
|
-
flatItems.push(subRec);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
else {
|
|
708
|
-
flatItems.push(rec);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
return flatItems
|
|
712
|
-
.map(item => {
|
|
713
|
-
// Resolve file path and line number from various shapes
|
|
714
|
-
let filePath = item['filePath']
|
|
715
|
-
?? item['file_path']
|
|
716
|
-
?? item['file'];
|
|
717
|
-
let lineNumber = typeof item['lineNumber'] === 'number'
|
|
718
|
-
? item['lineNumber']
|
|
719
|
-
: typeof item['line_number'] === 'number'
|
|
720
|
-
? item['line_number']
|
|
721
|
-
: typeof item['line'] === 'number'
|
|
722
|
-
? item['line']
|
|
723
|
-
: typeof item['line_start'] === 'number'
|
|
724
|
-
? item['line_start']
|
|
725
|
-
: undefined;
|
|
726
|
-
// Handle line as string: "24-50" or "24"
|
|
727
|
-
if (lineNumber === undefined && typeof item['line'] === 'string') {
|
|
728
|
-
const lineMatch = item['line'].match(/^(\d+)/);
|
|
729
|
-
if (lineMatch)
|
|
730
|
-
lineNumber = parseInt(lineMatch[1], 10);
|
|
731
|
-
}
|
|
732
|
-
if (lineNumber === undefined && typeof item['line_number'] === 'string') {
|
|
733
|
-
const lineMatch = item['line_number'].match(/^(\d+)/);
|
|
734
|
-
if (lineMatch)
|
|
735
|
-
lineNumber = parseInt(lineMatch[1], 10);
|
|
736
|
-
}
|
|
737
|
-
if (lineNumber === undefined && typeof item['lineNumber'] === 'string') {
|
|
738
|
-
const lineMatch = item['lineNumber'].match(/^(\d+)/);
|
|
739
|
-
if (lineMatch)
|
|
740
|
-
lineNumber = parseInt(lineMatch[1], 10);
|
|
741
|
-
}
|
|
742
|
-
// Handle combined fields: "file_line" or "location" like "src/foo.ts:42-50"
|
|
743
|
-
for (const combinedKey of ['file_line', 'location']) {
|
|
744
|
-
if (!filePath && typeof item[combinedKey] === 'string') {
|
|
745
|
-
const flMatch = item[combinedKey].match(/^([\w/.@-]+\.\w+):(\d+)/);
|
|
746
|
-
if (flMatch) {
|
|
747
|
-
filePath = flMatch[1];
|
|
748
|
-
lineNumber = lineNumber ?? parseInt(flMatch[2], 10);
|
|
749
|
-
}
|
|
750
|
-
else if (combinedKey === 'file_line') {
|
|
751
|
-
filePath = item[combinedKey];
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
// Handle locations array: [{ file: "...", line_start: N }]
|
|
756
|
-
if (!filePath && Array.isArray(item['locations']) && item['locations'].length > 0) {
|
|
757
|
-
const loc = this.asRecord(item['locations'][0]);
|
|
758
|
-
if (loc) {
|
|
759
|
-
filePath = loc['file'] ?? loc['filePath'];
|
|
760
|
-
lineNumber = lineNumber ?? (typeof loc['line_start'] === 'number' ? loc['line_start'] : undefined);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
// Resolve title: prefer explicit title/message, fall back to issue/summary/description/name
|
|
764
|
-
const hasExplicitTitle = item['title'] !== undefined || item['message'] !== undefined
|
|
765
|
-
|| item['issue'] !== undefined || item['summary'] !== undefined;
|
|
766
|
-
const title = String(item['title'] ?? item['message'] ?? item['issue'] ?? item['summary']
|
|
767
|
-
?? item['name'] ?? item['description'] ?? 'Untitled Issue');
|
|
768
|
-
// For description: if title consumed 'description', use explanation/suggestion/recommendation instead
|
|
769
|
-
const detailsStr = typeof item['details'] === 'string' ? item['details'] : undefined;
|
|
770
|
-
const description = hasExplicitTitle
|
|
771
|
-
? String(item['description'] ?? detailsStr ?? item['explanation'] ?? item['suggestion'] ?? item['recommendation'] ?? '')
|
|
772
|
-
: String(item['explanation'] ?? detailsStr ?? item['suggestion'] ?? item['recommendation'] ?? item['description'] ?? '');
|
|
773
|
-
return {
|
|
774
|
-
title,
|
|
775
|
-
priority: this.normalizePriority(item['priority'] ?? item['type']),
|
|
776
|
-
severity: this.normalizeSeverity(item['severity']),
|
|
777
|
-
failureCode: item['failureCode']
|
|
778
|
-
?? item['failure_code']
|
|
779
|
-
?? item['code'],
|
|
780
|
-
filePath,
|
|
781
|
-
lineNumber,
|
|
782
|
-
description,
|
|
783
|
-
};
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
parseArtifacts(raw) {
|
|
787
|
-
return raw
|
|
788
|
-
.filter((item) => typeof item === 'object' && item !== null)
|
|
789
|
-
.map(item => ({
|
|
790
|
-
name: String(item['name'] ?? 'Untitled'),
|
|
791
|
-
path: String(item['path'] ?? ''),
|
|
792
|
-
size: typeof item['size'] === 'number' ? item['size'] : undefined,
|
|
793
|
-
contentType: item['contentType'] ?? item['content_type'],
|
|
794
|
-
}));
|
|
795
|
-
}
|
|
796
|
-
normalizePriority(value) {
|
|
797
|
-
const str = String(value ?? 'suggested').toLowerCase();
|
|
798
|
-
if (['critical', 'high', 'p0'].includes(str))
|
|
799
|
-
return 'critical';
|
|
800
|
-
if (['backlog', 'low', 'p2'].includes(str))
|
|
801
|
-
return 'backlog';
|
|
802
|
-
return 'suggested';
|
|
803
|
-
}
|
|
804
|
-
normalizeSeverity(value) {
|
|
805
|
-
const str = String(value ?? 'medium').toLowerCase();
|
|
806
|
-
if (str === 'critical')
|
|
807
|
-
return 'critical';
|
|
808
|
-
if (str === 'high')
|
|
809
|
-
return 'high';
|
|
810
|
-
if (str === 'low')
|
|
811
|
-
return 'low';
|
|
812
|
-
if (['info', 'informational', 'note'].includes(str))
|
|
813
|
-
return 'info';
|
|
814
|
-
return 'medium';
|
|
815
|
-
}
|
|
816
311
|
}
|
|
817
312
|
//# sourceMappingURL=OutputExtractor.js.map
|