@agwab/pi-workflow 0.1.2 → 0.2.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 +7 -13
- package/dist/compiler.d.ts +2 -0
- package/dist/compiler.js +27 -2
- package/dist/engine.d.ts +2 -0
- package/dist/engine.js +3 -2
- package/dist/extension.js +201 -16
- package/dist/store.js +1 -0
- package/dist/types.d.ts +3 -0
- package/dist/workflow-progress-health.d.ts +37 -0
- package/dist/workflow-progress-health.js +296 -0
- package/dist/workflow-runtime.d.ts +6 -0
- package/dist/workflow-runtime.js +33 -10
- package/dist/workflow-view.d.ts +2 -0
- package/dist/workflow-view.js +97 -18
- package/dist/workflow-web-source.js +32 -14
- package/docs/usage.md +1 -1
- package/package.json +6 -6
- package/src/compiler.ts +41 -2
- package/src/engine.ts +7 -16
- package/src/extension.ts +254 -22
- package/src/store.ts +1 -0
- package/src/types.ts +4 -0
- package/src/workflow-progress-health.ts +461 -0
- package/src/workflow-runtime.ts +50 -13
- package/src/workflow-view.ts +186 -41
- package/src/workflow-web-source.ts +192 -69
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +111 -37
- package/workflows/deep-research/helpers/final-audit-packet.mjs +191 -14
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +159 -50
- package/workflows/deep-research/helpers/render-executive.mjs +671 -37
- package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +624 -0
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +2 -0
- package/workflows/deep-research/schemas/deep-research-final-synthesis-control.schema.json +110 -0
- package/workflows/deep-research/spec.json +41 -11
|
@@ -10,10 +10,11 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
|
|
12
12
|
function findSource(sources, stageId) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const entries = Object.entries(sources ?? {});
|
|
14
|
+
const exact = entries.find(([specId]) => specId === stageId);
|
|
15
|
+
if (exact) return exact[1];
|
|
16
|
+
const dotted = entries.find(([specId]) => specId.startsWith(`${stageId}.`));
|
|
17
|
+
return dotted?.[1] ?? null;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
function asArray(value) {
|
|
@@ -33,6 +34,9 @@ function flattenItems(value) {
|
|
|
33
34
|
"finding",
|
|
34
35
|
"claim",
|
|
35
36
|
"note",
|
|
37
|
+
"reason",
|
|
38
|
+
"nextStep",
|
|
39
|
+
"evidenceState",
|
|
36
40
|
"whyItMatters",
|
|
37
41
|
"parentImpact",
|
|
38
42
|
"recommendation",
|
|
@@ -46,6 +50,16 @@ function flattenItems(value) {
|
|
|
46
50
|
) {
|
|
47
51
|
return [value];
|
|
48
52
|
}
|
|
53
|
+
if (
|
|
54
|
+
value.id ||
|
|
55
|
+
value.gapId ||
|
|
56
|
+
value.slotId ||
|
|
57
|
+
Array.isArray(value.relatedFactSlotIds) ||
|
|
58
|
+
Array.isArray(value.sourceUrls) ||
|
|
59
|
+
Array.isArray(value.sourceRefs)
|
|
60
|
+
) {
|
|
61
|
+
return [value];
|
|
62
|
+
}
|
|
49
63
|
return Object.values(value).flatMap((item) => flattenItems(item));
|
|
50
64
|
}
|
|
51
65
|
|
|
@@ -83,6 +97,37 @@ function stringifyItem(item) {
|
|
|
83
97
|
return cleanText(String(item)) || "(empty item)";
|
|
84
98
|
}
|
|
85
99
|
|
|
100
|
+
function summaryText(report, fallback) {
|
|
101
|
+
const summary = report?.summary;
|
|
102
|
+
if (typeof summary === "string" && summary.trim()) return cleanText(summary);
|
|
103
|
+
if (isRecord(summary)) {
|
|
104
|
+
const parts = [
|
|
105
|
+
summary.directAnswer,
|
|
106
|
+
summary.answer,
|
|
107
|
+
summary.summary,
|
|
108
|
+
summary.finding,
|
|
109
|
+
]
|
|
110
|
+
.filter((value) => typeof value === "string" && value.trim())
|
|
111
|
+
.map(cleanText);
|
|
112
|
+
const confidence = cleanText(summary.confidence ?? "");
|
|
113
|
+
const caveat = cleanText(summary.keyCaveat ?? summary.caveat ?? "");
|
|
114
|
+
return (
|
|
115
|
+
[
|
|
116
|
+
parts[0],
|
|
117
|
+
confidence ? `Confidence: ${confidence}.` : undefined,
|
|
118
|
+
caveat ? `Key caveat: ${caveat}.` : undefined,
|
|
119
|
+
]
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
.join(" ") || stringifyItem(summary)
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return cleanText(fallback ?? "Research completed with audited evidence.");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function hasObjectSerializationArtifact(text) {
|
|
128
|
+
return /\[object Object\]/.test(String(text ?? ""));
|
|
129
|
+
}
|
|
130
|
+
|
|
86
131
|
function truncateWords(text, maxWords) {
|
|
87
132
|
const items = words(text);
|
|
88
133
|
if (items.length <= maxWords) return cleanText(text);
|
|
@@ -156,6 +201,61 @@ function urlsOf(item, limit = 3) {
|
|
|
156
201
|
return uniqueStructuredUrls(item).slice(0, limit);
|
|
157
202
|
}
|
|
158
203
|
|
|
204
|
+
function normalizeLocalRef(value) {
|
|
205
|
+
if (typeof value !== "string") return null;
|
|
206
|
+
const text = value.trim();
|
|
207
|
+
if (!text || /^https?:\/\//i.test(text) || isWorkflowSourceRefText(text))
|
|
208
|
+
return null;
|
|
209
|
+
const stripped = text.replace(/^(?:file|repo):/i, "");
|
|
210
|
+
if (!/[\w./-]+\.[\w]+(?:#L\d+(?:-L?\d+)?)?$/i.test(stripped)) return null;
|
|
211
|
+
return stripped;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function isWorkflowSourceRefText(value) {
|
|
215
|
+
return /^wsrc_[a-z0-9]{16,}$/i.test(String(value ?? "").trim());
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function collectLocalRefs(value, refs = []) {
|
|
219
|
+
if (!value || typeof value !== "object") return refs;
|
|
220
|
+
if (Array.isArray(value)) {
|
|
221
|
+
for (const item of value) collectLocalRefs(item, refs);
|
|
222
|
+
return refs;
|
|
223
|
+
}
|
|
224
|
+
for (const [key, item] of Object.entries(value)) {
|
|
225
|
+
if (/^(files?|paths?|sourceRefs?|sourceUrls?|sources?)$/i.test(key)) {
|
|
226
|
+
for (const candidate of asArray(item).length ? item : [item]) {
|
|
227
|
+
const ref = normalizeLocalRef(candidate);
|
|
228
|
+
if (ref) refs.push(ref);
|
|
229
|
+
else if (candidate && typeof candidate === "object")
|
|
230
|
+
collectLocalRefs(candidate, refs);
|
|
231
|
+
}
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (item && typeof item === "object") collectLocalRefs(item, refs);
|
|
235
|
+
}
|
|
236
|
+
return refs;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function localRefsOf(item, limit = 3) {
|
|
240
|
+
const out = [];
|
|
241
|
+
const seen = new Set();
|
|
242
|
+
for (const ref of collectLocalRefs(item, [])) {
|
|
243
|
+
if (seen.has(ref)) continue;
|
|
244
|
+
seen.add(ref);
|
|
245
|
+
out.push(ref);
|
|
246
|
+
if (out.length >= limit) break;
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function referenceList(item, limit = 3) {
|
|
252
|
+
const urls = markdownLinkList(urlsOf(item, limit), limit);
|
|
253
|
+
const localRefs = localRefsOf(item, limit)
|
|
254
|
+
.map((ref) => `\`${ref}\``)
|
|
255
|
+
.join(", ");
|
|
256
|
+
return [urls, localRefs].filter(Boolean).join("; ");
|
|
257
|
+
}
|
|
258
|
+
|
|
159
259
|
function markdownLinkList(urls, maxItems = 3) {
|
|
160
260
|
return urls
|
|
161
261
|
.slice(0, maxItems)
|
|
@@ -195,19 +295,54 @@ function finiteNumber(value) {
|
|
|
195
295
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
196
296
|
}
|
|
197
297
|
|
|
298
|
+
function normalizeClaimStatus(status) {
|
|
299
|
+
const text = cleanText(status).toLowerCase();
|
|
300
|
+
if (!text) return "";
|
|
301
|
+
if (text.includes("conflict")) return "conflicting";
|
|
302
|
+
if (text.includes("unsupported")) return "unsupported";
|
|
303
|
+
if (text.includes("partial")) return "partially_supported";
|
|
304
|
+
if (text.includes("verified")) return "verified";
|
|
305
|
+
return text;
|
|
306
|
+
}
|
|
307
|
+
|
|
198
308
|
function coverageCounts(coverage, fallback) {
|
|
199
309
|
if (!coverage || typeof coverage !== "object") return null;
|
|
200
|
-
|
|
310
|
+
const counts = {
|
|
201
311
|
total: finiteNumber(coverage.verificationCandidates) ?? fallback.total,
|
|
202
312
|
verified: finiteNumber(coverage.verified) ?? fallback.verified,
|
|
203
313
|
partially_supported:
|
|
204
|
-
finiteNumber(coverage.partiallySupported) ??
|
|
314
|
+
finiteNumber(coverage.partiallySupported) ??
|
|
315
|
+
finiteNumber(coverage.partially_supported) ??
|
|
316
|
+
fallback.partially_supported,
|
|
205
317
|
unsupported: finiteNumber(coverage.unsupported) ?? fallback.unsupported,
|
|
206
318
|
conflicting: finiteNumber(coverage.conflicting) ?? fallback.conflicting,
|
|
207
319
|
};
|
|
320
|
+
if (counts.total == null) {
|
|
321
|
+
counts.total =
|
|
322
|
+
counts.verified +
|
|
323
|
+
counts.partially_supported +
|
|
324
|
+
counts.unsupported +
|
|
325
|
+
counts.conflicting;
|
|
326
|
+
}
|
|
327
|
+
return counts;
|
|
208
328
|
}
|
|
209
329
|
|
|
210
|
-
function
|
|
330
|
+
function packetVerdictCounts(packet, fallback) {
|
|
331
|
+
const verdicts = packet?.verdictCounts;
|
|
332
|
+
if (!isRecord(verdicts)) return null;
|
|
333
|
+
const counts = coverageCounts(verdicts, fallback);
|
|
334
|
+
if (!counts) return null;
|
|
335
|
+
counts.total =
|
|
336
|
+
finiteNumber(packet?.invariantChecks?.candidateCount) ??
|
|
337
|
+
finiteNumber(verdicts.total) ??
|
|
338
|
+
counts.verified +
|
|
339
|
+
counts.partially_supported +
|
|
340
|
+
counts.unsupported +
|
|
341
|
+
counts.conflicting;
|
|
342
|
+
return counts;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function claimCounts(control, packet) {
|
|
211
346
|
const claims = asArray(control?.claimVerdictIndex?.claims);
|
|
212
347
|
const counts = {
|
|
213
348
|
total: claims.length,
|
|
@@ -217,9 +352,12 @@ function claimCounts(control) {
|
|
|
217
352
|
conflicting: 0,
|
|
218
353
|
};
|
|
219
354
|
for (const claim of claims) {
|
|
220
|
-
const status = claim?.status;
|
|
355
|
+
const status = normalizeClaimStatus(claim?.status);
|
|
221
356
|
if (status && Object.hasOwn(counts, status)) counts[status] += 1;
|
|
222
357
|
}
|
|
358
|
+
const packetCounts = packetVerdictCounts(packet, counts);
|
|
359
|
+
if (packetCounts) return packetCounts;
|
|
360
|
+
|
|
223
361
|
const coverage = coverageCounts(
|
|
224
362
|
control?.finalReport?.coverageSummary,
|
|
225
363
|
counts,
|
|
@@ -259,6 +397,326 @@ function factSlotSummary(factSlots) {
|
|
|
259
397
|
};
|
|
260
398
|
}
|
|
261
399
|
|
|
400
|
+
function stringArray(value, limit = Infinity) {
|
|
401
|
+
const out = [];
|
|
402
|
+
const seen = new Set();
|
|
403
|
+
for (const item of asArray(value)) {
|
|
404
|
+
if (typeof item !== "string") continue;
|
|
405
|
+
const text = item.trim();
|
|
406
|
+
if (!text || seen.has(text)) continue;
|
|
407
|
+
seen.add(text);
|
|
408
|
+
out.push(text);
|
|
409
|
+
if (out.length >= limit) break;
|
|
410
|
+
}
|
|
411
|
+
return out;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function uniqueStrings(values, limit = Infinity) {
|
|
415
|
+
const out = [];
|
|
416
|
+
const seen = new Set();
|
|
417
|
+
for (const value of values) {
|
|
418
|
+
if (typeof value !== "string") continue;
|
|
419
|
+
const text = value.trim();
|
|
420
|
+
if (!text || seen.has(text)) continue;
|
|
421
|
+
seen.add(text);
|
|
422
|
+
out.push(text);
|
|
423
|
+
if (out.length >= limit) break;
|
|
424
|
+
}
|
|
425
|
+
return out;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function packetOf(packetSource) {
|
|
429
|
+
return isRecord(packetSource?.packet) ? packetSource.packet : {};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function claimIdOf(row) {
|
|
433
|
+
return cleanText(row?.id ?? row?.claimId ?? "");
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function gapIdOf(row) {
|
|
437
|
+
return cleanText(row?.id ?? row?.gapId ?? "");
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function claimLedger(packet, control) {
|
|
441
|
+
const packetLedger = asArray(packet?.claimVerdictLedger);
|
|
442
|
+
return packetLedger.length
|
|
443
|
+
? packetLedger
|
|
444
|
+
: asArray(control?.claimVerdictIndex?.claims);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function mapById(rows, idFn) {
|
|
448
|
+
const out = new Map();
|
|
449
|
+
for (const row of rows) {
|
|
450
|
+
const id = idFn(row);
|
|
451
|
+
if (id && !out.has(id)) out.set(id, row);
|
|
452
|
+
}
|
|
453
|
+
return out;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function numberedId(prefix, index) {
|
|
457
|
+
return `${prefix}-${String(index + 1).padStart(3, "0")}`;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function packetGapRows(packet) {
|
|
461
|
+
const remaining = asArray(packet?.remainingGaps).map((gap, index) => ({
|
|
462
|
+
id: gapIdOf(gap) || numberedId("gap-remaining", index),
|
|
463
|
+
kind: "Gap",
|
|
464
|
+
...gap,
|
|
465
|
+
}));
|
|
466
|
+
const coverage = asArray(packet?.coverageGaps).map((gap, index) => ({
|
|
467
|
+
id: gapIdOf(gap) || numberedId("gap-coverage", index),
|
|
468
|
+
kind: "Coverage gap",
|
|
469
|
+
...gap,
|
|
470
|
+
}));
|
|
471
|
+
return [...remaining, ...coverage];
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function rowsForIds(ids, rowById, warnings, label) {
|
|
475
|
+
const rows = [];
|
|
476
|
+
for (const id of ids) {
|
|
477
|
+
const row = rowById.get(id);
|
|
478
|
+
if (row) {
|
|
479
|
+
rows.push(row);
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
warnings.push({
|
|
483
|
+
section: "references",
|
|
484
|
+
label,
|
|
485
|
+
total: 1,
|
|
486
|
+
rendered: 0,
|
|
487
|
+
missingId: id,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return rows;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function claimSourceUrls(rows, limit = 8) {
|
|
494
|
+
return uniqueStrings(
|
|
495
|
+
rows.flatMap((row) => [...asArray(row?.sourceUrls), ...urlsOf(row, limit)]),
|
|
496
|
+
limit,
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function claimSourceRefs(rows, limit = 8) {
|
|
501
|
+
return uniqueStrings(
|
|
502
|
+
rows.flatMap((row) => asArray(row?.sourceRefs)),
|
|
503
|
+
limit,
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function evidenceStrength(status) {
|
|
508
|
+
switch (normalizeClaimStatus(status)) {
|
|
509
|
+
case "verified":
|
|
510
|
+
return 3;
|
|
511
|
+
case "partially_supported":
|
|
512
|
+
return 2;
|
|
513
|
+
case "unsupported":
|
|
514
|
+
return 1;
|
|
515
|
+
case "conflicting":
|
|
516
|
+
return 0;
|
|
517
|
+
default:
|
|
518
|
+
return -1;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function evidenceStatusFromRows(rows, fallback) {
|
|
523
|
+
if (rows.length === 0) return cleanText(fallback) || "not specified";
|
|
524
|
+
let weakest = "verified";
|
|
525
|
+
let weakestScore = Infinity;
|
|
526
|
+
for (const row of rows) {
|
|
527
|
+
const status = normalizeClaimStatus(row?.status ?? row?.verdict);
|
|
528
|
+
const score = evidenceStrength(status);
|
|
529
|
+
if (score >= 0 && score < weakestScore) {
|
|
530
|
+
weakest = status;
|
|
531
|
+
weakestScore = score;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return weakestScore === Infinity
|
|
535
|
+
? cleanText(fallback) || "not specified"
|
|
536
|
+
: weakest;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function claimToFinding(row) {
|
|
540
|
+
return {
|
|
541
|
+
id: claimIdOf(row),
|
|
542
|
+
finding: cleanText(row?.claim ?? row?.support ?? stringifyItem(row)),
|
|
543
|
+
evidenceStatus: normalizeClaimStatus(row?.status) || row?.status,
|
|
544
|
+
confidence: row?.confidence,
|
|
545
|
+
sourceUrls: asArray(row?.sourceUrls),
|
|
546
|
+
sourceRefs: asArray(row?.sourceRefs),
|
|
547
|
+
rationale: row?.support,
|
|
548
|
+
caveat: row?.caveat,
|
|
549
|
+
correctionOrCounterclaim: row?.correctionOrCounterclaim,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function supportingClaimIds(item) {
|
|
554
|
+
return uniqueStrings([
|
|
555
|
+
...asArray(item?.supportingClaimIds),
|
|
556
|
+
...asArray(item?.claimIds),
|
|
557
|
+
...asArray(item?.relatedClaimIds),
|
|
558
|
+
]);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function withSupportingEvidence(item, claimRows) {
|
|
562
|
+
return {
|
|
563
|
+
...item,
|
|
564
|
+
evidenceStatus: evidenceStatusFromRows(claimRows, item?.evidenceStatus),
|
|
565
|
+
sourceUrls: uniqueStrings(
|
|
566
|
+
[...asArray(item?.sourceUrls), ...claimSourceUrls(claimRows)],
|
|
567
|
+
8,
|
|
568
|
+
),
|
|
569
|
+
sourceRefs: uniqueStrings(
|
|
570
|
+
[...asArray(item?.sourceRefs), ...claimSourceRefs(claimRows)],
|
|
571
|
+
8,
|
|
572
|
+
),
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function coverageSummaryFromPacket(packet, fallback = {}) {
|
|
577
|
+
const counts = packetVerdictCounts(packet, {
|
|
578
|
+
total: 0,
|
|
579
|
+
verified: 0,
|
|
580
|
+
partially_supported: 0,
|
|
581
|
+
unsupported: 0,
|
|
582
|
+
conflicting: 0,
|
|
583
|
+
});
|
|
584
|
+
if (!counts) return fallback;
|
|
585
|
+
return {
|
|
586
|
+
...fallback,
|
|
587
|
+
verified: counts.verified,
|
|
588
|
+
partiallySupported: counts.partially_supported,
|
|
589
|
+
unsupported: counts.unsupported,
|
|
590
|
+
conflicting: counts.conflicting,
|
|
591
|
+
verificationCandidates: counts.total,
|
|
592
|
+
depth: packet?.researchMetadataSeed?.depth ?? fallback.depth,
|
|
593
|
+
researchQuestions:
|
|
594
|
+
packet?.researchMetadataSeed?.researchQuestions ??
|
|
595
|
+
fallback.researchQuestions,
|
|
596
|
+
preserved:
|
|
597
|
+
packet?.overflowLedger?.preservedClaimCount ?? fallback.preserved,
|
|
598
|
+
coverageGaps:
|
|
599
|
+
packet?.overflowLedger?.coverageGapCount ?? fallback.coverageGaps,
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function composeResearchReport(control, packetSource) {
|
|
604
|
+
const packet = packetOf(packetSource);
|
|
605
|
+
const legacyReport = control?.finalReport ?? {};
|
|
606
|
+
const synthesis = isRecord(control?.synthesis) ? control.synthesis : null;
|
|
607
|
+
const ledger = claimLedger(packet, control);
|
|
608
|
+
const claimById = mapById(ledger, claimIdOf);
|
|
609
|
+
const gapRows = packetGapRows(packet);
|
|
610
|
+
const gapById = mapById(gapRows, gapIdOf);
|
|
611
|
+
const warnings = [];
|
|
612
|
+
|
|
613
|
+
if (!synthesis) {
|
|
614
|
+
const report = { ...legacyReport };
|
|
615
|
+
if (asArray(packet.factSlotCoverage).length > 0)
|
|
616
|
+
report.factSlotCoverage = packet.factSlotCoverage;
|
|
617
|
+
if (isRecord(packet.researchMetadataSeed))
|
|
618
|
+
report.researchMetadata = packet.researchMetadataSeed;
|
|
619
|
+
if (isRecord(packet.verdictCounts))
|
|
620
|
+
report.coverageSummary = coverageSummaryFromPacket(
|
|
621
|
+
packet,
|
|
622
|
+
report.coverageSummary,
|
|
623
|
+
);
|
|
624
|
+
if (asArray(report.remainingGaps).length === 0 && gapRows.length > 0)
|
|
625
|
+
report.remainingGaps = gapRows;
|
|
626
|
+
if (
|
|
627
|
+
asArray(report.researchScopeCoverage).length === 0 &&
|
|
628
|
+
asArray(packet.researchScopeCoverage).length > 0
|
|
629
|
+
) {
|
|
630
|
+
report.researchScopeCoverage = packet.researchScopeCoverage;
|
|
631
|
+
}
|
|
632
|
+
return { report, packet, ledger, warnings };
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const keyFindingIds = stringArray(synthesis.keyFindingIds, 12);
|
|
636
|
+
const keyFindingRows = keyFindingIds.length
|
|
637
|
+
? rowsForIds(keyFindingIds, claimById, warnings, "key findings")
|
|
638
|
+
: ledger
|
|
639
|
+
.filter((row) => normalizeClaimStatus(row?.status) === "verified")
|
|
640
|
+
.slice(0, 8);
|
|
641
|
+
const mapOverlayItems = (items, textField) =>
|
|
642
|
+
asArray(items).map((item) => {
|
|
643
|
+
const ids = supportingClaimIds(item);
|
|
644
|
+
const rows = rowsForIds(ids, claimById, warnings, textField);
|
|
645
|
+
return withSupportingEvidence(item, rows);
|
|
646
|
+
});
|
|
647
|
+
const caveatNotes = asArray(synthesis.caveatNotes).map((item) => {
|
|
648
|
+
const rows = rowsForIds(
|
|
649
|
+
supportingClaimIds(item),
|
|
650
|
+
claimById,
|
|
651
|
+
warnings,
|
|
652
|
+
"caveat notes",
|
|
653
|
+
);
|
|
654
|
+
const gaps = rowsForIds(
|
|
655
|
+
stringArray(item?.gapIds, 12),
|
|
656
|
+
gapById,
|
|
657
|
+
warnings,
|
|
658
|
+
"gap notes",
|
|
659
|
+
);
|
|
660
|
+
return withSupportingEvidence(
|
|
661
|
+
{
|
|
662
|
+
...item,
|
|
663
|
+
relatedGaps: gaps,
|
|
664
|
+
},
|
|
665
|
+
rows,
|
|
666
|
+
);
|
|
667
|
+
});
|
|
668
|
+
const optionalUnsupported = rowsForIds(
|
|
669
|
+
stringArray(synthesis.notableUnsupportedClaimIds, 12),
|
|
670
|
+
claimById,
|
|
671
|
+
warnings,
|
|
672
|
+
"unsupported claims",
|
|
673
|
+
).map(claimToFinding);
|
|
674
|
+
const optionalContested = rowsForIds(
|
|
675
|
+
stringArray(synthesis.contestedClaimIds, 12),
|
|
676
|
+
claimById,
|
|
677
|
+
warnings,
|
|
678
|
+
"contested claims",
|
|
679
|
+
).map(claimToFinding);
|
|
680
|
+
const derivedUnsupported = ledger
|
|
681
|
+
.filter((row) => normalizeClaimStatus(row?.status) === "unsupported")
|
|
682
|
+
.map(claimToFinding);
|
|
683
|
+
const derivedContested = ledger
|
|
684
|
+
.filter((row) => normalizeClaimStatus(row?.status) === "conflicting")
|
|
685
|
+
.map(claimToFinding);
|
|
686
|
+
|
|
687
|
+
return {
|
|
688
|
+
report: {
|
|
689
|
+
summary: synthesis.bottomLine ?? control?.digest,
|
|
690
|
+
researchMetadata: packet.researchMetadataSeed ?? {},
|
|
691
|
+
coverageSummary: coverageSummaryFromPacket(packet, {}),
|
|
692
|
+
factSlotCoverage: asArray(packet.factSlotCoverage),
|
|
693
|
+
mainFindings: keyFindingRows.map(claimToFinding),
|
|
694
|
+
recommendations: mapOverlayItems(
|
|
695
|
+
synthesis.recommendations,
|
|
696
|
+
"recommendations",
|
|
697
|
+
),
|
|
698
|
+
actionPlan: mapOverlayItems(synthesis.actionPlan, "action plan"),
|
|
699
|
+
caveatedFindings: caveatNotes,
|
|
700
|
+
contestedAreas: optionalContested.length
|
|
701
|
+
? optionalContested
|
|
702
|
+
: derivedContested,
|
|
703
|
+
notableUnsupportedClaims: optionalUnsupported.length
|
|
704
|
+
? optionalUnsupported
|
|
705
|
+
: derivedUnsupported,
|
|
706
|
+
unverifiedButRelevant: asArray(packet.preservedClaims),
|
|
707
|
+
parentDecisionNotes: mapOverlayItems(
|
|
708
|
+
synthesis.parentDecisionNotes,
|
|
709
|
+
"decision notes",
|
|
710
|
+
),
|
|
711
|
+
researchScopeCoverage: asArray(packet.researchScopeCoverage),
|
|
712
|
+
remainingGaps: gapRows,
|
|
713
|
+
},
|
|
714
|
+
packet,
|
|
715
|
+
ledger,
|
|
716
|
+
warnings,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
262
720
|
function statusRank(item) {
|
|
263
721
|
const status =
|
|
264
722
|
`${item?.evidenceStatus ?? item?.status ?? item?.confidence ?? ""}`.toLowerCase();
|
|
@@ -295,9 +753,7 @@ function renderEvidenceStrength(report) {
|
|
|
295
753
|
slot.label ?? slot.slotId ?? slot.bestValue ?? "Evidence area",
|
|
296
754
|
);
|
|
297
755
|
const status = escapeTableCell(evidenceStatusOf(slot));
|
|
298
|
-
const evidence = escapeTableCell(
|
|
299
|
-
markdownLinkList(urlsOf(slot, 2), 2) || "—",
|
|
300
|
-
);
|
|
756
|
+
const evidence = escapeTableCell(referenceList(slot, 2) || "—");
|
|
301
757
|
const impact = escapeTableCell(
|
|
302
758
|
slot.parentImpact ?? slot.whyItMatters ?? slot.notes ?? "",
|
|
303
759
|
);
|
|
@@ -353,7 +809,7 @@ function renderMainFindings(report) {
|
|
|
353
809
|
findings.forEach(({ item: finding, text }, index) => {
|
|
354
810
|
const status = evidenceStatusOf(finding);
|
|
355
811
|
const confidence = confidenceOf(finding);
|
|
356
|
-
const urls =
|
|
812
|
+
const urls = referenceList(finding, 4);
|
|
357
813
|
out.push(`### ${index + 1}. ${text}`);
|
|
358
814
|
out.push("");
|
|
359
815
|
out.push(
|
|
@@ -378,7 +834,7 @@ function renderRecommendations(report) {
|
|
|
378
834
|
const out = ["## Recommendations", ""];
|
|
379
835
|
recommendations.forEach(({ item, text }, index) => {
|
|
380
836
|
const status = evidenceStatusOf(item);
|
|
381
|
-
const urls =
|
|
837
|
+
const urls = referenceList(item, 4);
|
|
382
838
|
out.push(`${index + 1}. **${text}**`);
|
|
383
839
|
out.push(` - Evidence status: ${status || "not specified"}`);
|
|
384
840
|
if (urls) out.push(` - Sources: ${urls}`);
|
|
@@ -394,7 +850,7 @@ function renderActionPlan(report) {
|
|
|
394
850
|
actions.forEach(({ item, text }, index) => {
|
|
395
851
|
const numericStep = Number(item?.step);
|
|
396
852
|
const step = Number.isFinite(numericStep) ? numericStep : index + 1;
|
|
397
|
-
const urls =
|
|
853
|
+
const urls = referenceList(item, 3);
|
|
398
854
|
const evidence = evidenceStatusOf(item);
|
|
399
855
|
out.push(`${step}. ${text}`);
|
|
400
856
|
if (evidence && evidence !== "not specified")
|
|
@@ -405,21 +861,52 @@ function renderActionPlan(report) {
|
|
|
405
861
|
return out;
|
|
406
862
|
}
|
|
407
863
|
|
|
864
|
+
function fallbackCaveatText(item) {
|
|
865
|
+
if (!isRecord(item)) return stringifyItem(item);
|
|
866
|
+
const id = cleanText(item.id ?? item.gapId ?? "");
|
|
867
|
+
const slotIds = uniqueStrings([
|
|
868
|
+
item.slotId,
|
|
869
|
+
...asArray(item.relatedFactSlotIds),
|
|
870
|
+
]).join(", ");
|
|
871
|
+
const kind = cleanText(item.kind ?? "gap");
|
|
872
|
+
return (
|
|
873
|
+
[kind, id, slotIds ? `related slots: ${slotIds}` : undefined]
|
|
874
|
+
.filter(Boolean)
|
|
875
|
+
.join(" — ") || stringifyItem(item)
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
408
879
|
function caveatText(item) {
|
|
409
880
|
return itemText(
|
|
410
881
|
item,
|
|
411
|
-
[
|
|
412
|
-
|
|
882
|
+
[
|
|
883
|
+
"gap",
|
|
884
|
+
"finding",
|
|
885
|
+
"claim",
|
|
886
|
+
"note",
|
|
887
|
+
"reason",
|
|
888
|
+
"nextStep",
|
|
889
|
+
"evidenceState",
|
|
890
|
+
"whyItMatters",
|
|
891
|
+
"parentImpact",
|
|
892
|
+
],
|
|
893
|
+
fallbackCaveatText(item),
|
|
413
894
|
);
|
|
414
895
|
}
|
|
415
896
|
|
|
416
897
|
function caveatCategories(report) {
|
|
417
898
|
return [
|
|
418
899
|
{ kind: "Gap", items: flattenItems(report.remainingGaps) },
|
|
419
|
-
{
|
|
900
|
+
{
|
|
901
|
+
kind: "Unsupported",
|
|
902
|
+
items: flattenItems(report.notableUnsupportedClaims),
|
|
903
|
+
},
|
|
420
904
|
{ kind: "Contested", items: flattenItems(report.contestedAreas) },
|
|
421
905
|
{ kind: "Caveat", items: flattenItems(report.caveatedFindings) },
|
|
422
|
-
{
|
|
906
|
+
{
|
|
907
|
+
kind: "Unverified lead",
|
|
908
|
+
items: flattenItems(report.unverifiedButRelevant),
|
|
909
|
+
},
|
|
423
910
|
{ kind: "Decision note", items: flattenItems(report.parentDecisionNotes) },
|
|
424
911
|
]
|
|
425
912
|
.map((category) => ({
|
|
@@ -450,7 +937,7 @@ function renderCaveats(report) {
|
|
|
450
937
|
if (selection.total === 0) return [];
|
|
451
938
|
const out = ["## Caveats and remaining gaps", ""];
|
|
452
939
|
for (const { kind, item, text } of selection.selected) {
|
|
453
|
-
const urls =
|
|
940
|
+
const urls = referenceList(item, 3);
|
|
454
941
|
out.push(`- **${kind}:** ${text}${urls ? ` (${urls})` : ""}`);
|
|
455
942
|
}
|
|
456
943
|
out.push("");
|
|
@@ -475,8 +962,8 @@ function renderSourceIndex(sourceIndex) {
|
|
|
475
962
|
return out;
|
|
476
963
|
}
|
|
477
964
|
|
|
478
|
-
function renderAuditSummary(
|
|
479
|
-
const coverage =
|
|
965
|
+
function renderAuditSummary(report, claimSummary, slots) {
|
|
966
|
+
const coverage = report?.coverageSummary ?? {};
|
|
480
967
|
const mismatches = asArray(claimSummary.coverageSummaryMismatch);
|
|
481
968
|
return [
|
|
482
969
|
"## Audit summary",
|
|
@@ -493,7 +980,7 @@ function renderAuditSummary(control, claimSummary, slots) {
|
|
|
493
980
|
...(coverage.researchQuestions != null
|
|
494
981
|
? [`- Research questions: ${coverage.researchQuestions}.`]
|
|
495
982
|
: []),
|
|
496
|
-
"- Audit artifact: `
|
|
983
|
+
"- Audit artifact: `audit.md`.",
|
|
497
984
|
"",
|
|
498
985
|
];
|
|
499
986
|
}
|
|
@@ -521,9 +1008,10 @@ function renderWarnings(sectionCounts) {
|
|
|
521
1008
|
}));
|
|
522
1009
|
}
|
|
523
1010
|
|
|
524
|
-
function renderResearchMarkdown(control, options = {}) {
|
|
525
|
-
const
|
|
526
|
-
const
|
|
1011
|
+
function renderResearchMarkdown(control, packetSource, options = {}) {
|
|
1012
|
+
const composed = composeResearchReport(control, packetSource);
|
|
1013
|
+
const report = composed.report;
|
|
1014
|
+
const claimSummary = claimCounts(control, composed.packet);
|
|
527
1015
|
const factSlots = sortedFactSlots(report);
|
|
528
1016
|
const slots = factSlotSummary(asArray(report.factSlotCoverage));
|
|
529
1017
|
const findings = mainFindingEntries(report);
|
|
@@ -541,7 +1029,7 @@ function renderResearchMarkdown(control, options = {}) {
|
|
|
541
1029
|
report.remainingGaps,
|
|
542
1030
|
report.parentDecisionNotes,
|
|
543
1031
|
report.unverifiedButRelevant,
|
|
544
|
-
|
|
1032
|
+
composed.ledger,
|
|
545
1033
|
);
|
|
546
1034
|
const maxUrls = Number.isFinite(Number(options.maxUrls))
|
|
547
1035
|
? Math.max(0, Number(options.maxUrls))
|
|
@@ -569,18 +1057,14 @@ function renderResearchMarkdown(control, options = {}) {
|
|
|
569
1057
|
sourceUrls: allSourceIndex.length,
|
|
570
1058
|
renderedSourceUrls: sourceIndex.length,
|
|
571
1059
|
};
|
|
572
|
-
const warnings = renderWarnings(sectionCounts);
|
|
1060
|
+
const warnings = [...renderWarnings(sectionCounts), ...composed.warnings];
|
|
573
1061
|
|
|
574
1062
|
const sections = [
|
|
575
1063
|
"# Research report",
|
|
576
1064
|
"",
|
|
577
1065
|
"## Bottom line",
|
|
578
1066
|
"",
|
|
579
|
-
|
|
580
|
-
report.summary ??
|
|
581
|
-
control.digest ??
|
|
582
|
-
"Research completed with audited evidence.",
|
|
583
|
-
),
|
|
1067
|
+
summaryText(report, control.digest),
|
|
584
1068
|
"",
|
|
585
1069
|
...renderEvidenceStrength(report),
|
|
586
1070
|
...renderMainFindings(report),
|
|
@@ -588,7 +1072,7 @@ function renderResearchMarkdown(control, options = {}) {
|
|
|
588
1072
|
...renderActionPlan(report),
|
|
589
1073
|
...renderCaveats(report),
|
|
590
1074
|
...renderSourceIndex(sourceIndex),
|
|
591
|
-
...renderAuditSummary(
|
|
1075
|
+
...renderAuditSummary(report, claimSummary, slots),
|
|
592
1076
|
];
|
|
593
1077
|
|
|
594
1078
|
const markdown = sections
|
|
@@ -610,10 +1094,128 @@ function stripLeadingHeading(markdown) {
|
|
|
610
1094
|
return String(markdown ?? "").replace(/^#\s+[^\n]+\n*/i, "");
|
|
611
1095
|
}
|
|
612
1096
|
|
|
613
|
-
|
|
1097
|
+
function synthesisClaimRows(control, rows) {
|
|
1098
|
+
const synthesis = isRecord(control?.synthesis) ? control.synthesis : null;
|
|
1099
|
+
if (!synthesis) return asArray(control?.claimVerdictIndex?.claims);
|
|
1100
|
+
const rowById = mapById(rows, claimIdOf);
|
|
1101
|
+
const ids = uniqueStrings([
|
|
1102
|
+
...asArray(synthesis.keyFindingIds),
|
|
1103
|
+
...asArray(synthesis.notableUnsupportedClaimIds),
|
|
1104
|
+
...asArray(synthesis.contestedClaimIds),
|
|
1105
|
+
...asArray(synthesis.recommendations).flatMap(supportingClaimIds),
|
|
1106
|
+
...asArray(synthesis.actionPlan).flatMap(supportingClaimIds),
|
|
1107
|
+
...asArray(synthesis.caveatNotes).flatMap(supportingClaimIds),
|
|
1108
|
+
...asArray(synthesis.parentDecisionNotes).flatMap(supportingClaimIds),
|
|
1109
|
+
]);
|
|
1110
|
+
return ids.map((id) => rowById.get(id)).filter(Boolean);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
function renderAuditMarkdown(control, packetSource, rendered) {
|
|
1114
|
+
const packet = packetSource?.packet ?? {};
|
|
1115
|
+
const report = control?.finalReport ?? {};
|
|
1116
|
+
const ledger = asArray(packet.claimVerdictLedger);
|
|
1117
|
+
const claims = synthesisClaimRows(control, ledger);
|
|
1118
|
+
const gaps = asArray(packet.remainingGaps).length
|
|
1119
|
+
? asArray(packet.remainingGaps)
|
|
1120
|
+
: asArray(report.remainingGaps);
|
|
1121
|
+
const sourceRefJoinFailures = asArray(packet.sourceRefJoinFailures).filter(
|
|
1122
|
+
(failure) => uniqueStructuredUrls(failure).length > 0,
|
|
1123
|
+
);
|
|
1124
|
+
const factSlots = asArray(packet.factSlotCoverage).length
|
|
1125
|
+
? asArray(packet.factSlotCoverage)
|
|
1126
|
+
: asArray(report.factSlotCoverage);
|
|
1127
|
+
const rows = ledger.length ? ledger : claims;
|
|
1128
|
+
const out = [
|
|
1129
|
+
"# Research audit",
|
|
1130
|
+
"",
|
|
1131
|
+
"This artifact preserves the detailed claim/gap/source ledger behind `executive.md`.",
|
|
1132
|
+
"",
|
|
1133
|
+
"## Claim verdict ledger",
|
|
1134
|
+
"",
|
|
1135
|
+
];
|
|
1136
|
+
if (rows.length > 0) {
|
|
1137
|
+
out.push(
|
|
1138
|
+
"| ID | Status | Claim/support | Caveat/source |",
|
|
1139
|
+
"|---|---|---|---|",
|
|
1140
|
+
);
|
|
1141
|
+
for (const row of rows) {
|
|
1142
|
+
const id = escapeTableCell(row.id ?? row.claimId ?? "—");
|
|
1143
|
+
const status = escapeTableCell(row.status ?? row.confidence ?? "—");
|
|
1144
|
+
const support = escapeTableCell(
|
|
1145
|
+
row.claim ??
|
|
1146
|
+
row.support ??
|
|
1147
|
+
row.verdictDigest?.support ??
|
|
1148
|
+
stringifyItem(row),
|
|
1149
|
+
);
|
|
1150
|
+
const caveat = escapeTableCell(
|
|
1151
|
+
row.caveat ??
|
|
1152
|
+
row.correctionOrCounterclaim ??
|
|
1153
|
+
markdownLinkList(urlsOf(row, 3), 3) ??
|
|
1154
|
+
"—",
|
|
1155
|
+
);
|
|
1156
|
+
out.push(`| ${id} | ${status} | ${support} | ${caveat || "—"} |`);
|
|
1157
|
+
}
|
|
1158
|
+
} else {
|
|
1159
|
+
out.push("No compact claim ledger was provided.");
|
|
1160
|
+
}
|
|
1161
|
+
out.push("", "## Fact slot coverage", "");
|
|
1162
|
+
if (factSlots.length > 0) {
|
|
1163
|
+
out.push(
|
|
1164
|
+
"| Slot | Status | Best value | Gap/impact |",
|
|
1165
|
+
"|---|---|---|---|",
|
|
1166
|
+
);
|
|
1167
|
+
for (const slot of factSlots) {
|
|
1168
|
+
out.push(
|
|
1169
|
+
`| ${escapeTableCell(slot.slotId ?? slot.label ?? "—")} | ${escapeTableCell(slot.status ?? "—")} | ${escapeTableCell(isRecord(slot.bestValue) ? stringifyItem(slot.bestValue) : (slot.bestValue ?? "—"))} | ${escapeTableCell(slot.gapReason || slot.parentImpact || "—")} |`,
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
} else {
|
|
1173
|
+
out.push("No fact-slot ledger was provided.");
|
|
1174
|
+
}
|
|
1175
|
+
out.push("", "## Remaining gaps", "");
|
|
1176
|
+
if (gaps.length > 0) {
|
|
1177
|
+
for (const gap of gaps)
|
|
1178
|
+
out.push(`- ${caveatText(gap) || stringifyItem(gap)}`);
|
|
1179
|
+
} else {
|
|
1180
|
+
out.push("No remaining gaps were reported.");
|
|
1181
|
+
}
|
|
1182
|
+
if (claims.length > 0 && ledger.length > 0) {
|
|
1183
|
+
out.push("", "## Claims used in executive synthesis", "");
|
|
1184
|
+
for (const claim of claims) {
|
|
1185
|
+
out.push(
|
|
1186
|
+
`- **${cleanText(claim.id ?? "claim")}** (${cleanText(claim.status ?? "unknown")}): ${cleanText(claim.claim ?? claim.support ?? stringifyItem(claim))}`,
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
if (sourceRefJoinFailures.length > 0) {
|
|
1191
|
+
out.push("", "## Source reference join failures", "");
|
|
1192
|
+
for (const failure of sourceRefJoinFailures)
|
|
1193
|
+
out.push(`- ${caveatText(failure) || stringifyItem(failure)}`);
|
|
1194
|
+
}
|
|
1195
|
+
out.push(
|
|
1196
|
+
"",
|
|
1197
|
+
"## Renderer diagnostics",
|
|
1198
|
+
"",
|
|
1199
|
+
`- Executive word count: ${countWords(rendered.markdown)}.`,
|
|
1200
|
+
`- Rendered source URLs: ${rendered.sourceIndex.length}/${rendered.allSourceIndex.length}.`,
|
|
1201
|
+
`- Render warnings: ${rendered.renderWarnings.length}.`,
|
|
1202
|
+
"",
|
|
1203
|
+
);
|
|
1204
|
+
return out
|
|
1205
|
+
.join("\n")
|
|
1206
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
1207
|
+
.trim();
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
export default async function renderExecutive({
|
|
1211
|
+
sources,
|
|
1212
|
+
options = {},
|
|
1213
|
+
context = {},
|
|
1214
|
+
}) {
|
|
614
1215
|
const control =
|
|
615
1216
|
findSource(sources, "final-audit") ??
|
|
616
1217
|
sources?.[Object.keys(sources ?? {})[0]];
|
|
1218
|
+
const auditPacket = findSource(sources, "final-audit-packet");
|
|
617
1219
|
if (!control || typeof control !== "object") {
|
|
618
1220
|
return {
|
|
619
1221
|
schema: "deep-research-executive-render-v1",
|
|
@@ -623,13 +1225,32 @@ export default async function renderExecutive({ sources, options = {}, context =
|
|
|
623
1225
|
blockers: ["missing final-audit control source"],
|
|
624
1226
|
executiveMarkdown: "",
|
|
625
1227
|
reportMarkdown: "",
|
|
1228
|
+
auditMarkdown: "",
|
|
626
1229
|
wordCount: 0,
|
|
627
1230
|
sourceUrlCount: 0,
|
|
1231
|
+
totalSourceUrlCount: 0,
|
|
1232
|
+
sourceUrls: [],
|
|
1233
|
+
sourceIndex: [],
|
|
1234
|
+
claimSummary: {
|
|
1235
|
+
total: 0,
|
|
1236
|
+
verified: 0,
|
|
1237
|
+
partially_supported: 0,
|
|
1238
|
+
unsupported: 0,
|
|
1239
|
+
conflicting: 0,
|
|
1240
|
+
},
|
|
1241
|
+
factSlotSummary: {
|
|
1242
|
+
total: 0,
|
|
1243
|
+
filled: 0,
|
|
1244
|
+
partial: 0,
|
|
1245
|
+
missingOrConflicting: 0,
|
|
1246
|
+
},
|
|
1247
|
+
sectionCounts: {},
|
|
628
1248
|
renderWarnings: [],
|
|
629
1249
|
gates: {
|
|
630
1250
|
renderedAllStructuredItems: false,
|
|
631
1251
|
passed: false,
|
|
632
1252
|
},
|
|
1253
|
+
auditArtifact: "final-audit.control.json",
|
|
633
1254
|
};
|
|
634
1255
|
}
|
|
635
1256
|
|
|
@@ -650,13 +1271,17 @@ export default async function renderExecutive({ sources, options = {}, context =
|
|
|
650
1271
|
? Math.max(0, Number(options.maxGaps))
|
|
651
1272
|
: undefined,
|
|
652
1273
|
};
|
|
653
|
-
const rendered = renderResearchMarkdown(control, opts);
|
|
1274
|
+
const rendered = renderResearchMarkdown(control, auditPacket, opts);
|
|
654
1275
|
let markdown = rendered.markdown;
|
|
655
1276
|
let truncated = false;
|
|
656
1277
|
if (Number.isFinite(opts.maxWords) && countWords(markdown) > opts.maxWords) {
|
|
657
1278
|
truncated = true;
|
|
658
1279
|
markdown = truncateWords(markdown, opts.maxWords);
|
|
659
1280
|
}
|
|
1281
|
+
const auditMarkdown = renderAuditMarkdown(control, auditPacket, rendered);
|
|
1282
|
+
const serializationArtifact =
|
|
1283
|
+
hasObjectSerializationArtifact(markdown) ||
|
|
1284
|
+
hasObjectSerializationArtifact(auditMarkdown);
|
|
660
1285
|
const wordCount = countWords(markdown);
|
|
661
1286
|
const sourceUrlCount = rendered.sourceIndex.length;
|
|
662
1287
|
const substantiveRenderWarnings = rendered.renderWarnings.filter(
|
|
@@ -665,10 +1290,14 @@ export default async function renderExecutive({ sources, options = {}, context =
|
|
|
665
1290
|
const renderedAllStructuredItems = substantiveRenderWarnings.length === 0;
|
|
666
1291
|
const truncatedWithOpenGaps =
|
|
667
1292
|
truncated && Number(rendered.sectionCounts.caveatsAndGaps ?? 0) > 0;
|
|
668
|
-
const passed =
|
|
1293
|
+
const passed =
|
|
1294
|
+
renderedAllStructuredItems &&
|
|
1295
|
+
!truncatedWithOpenGaps &&
|
|
1296
|
+
!serializationArtifact;
|
|
669
1297
|
|
|
670
1298
|
let executiveSidecarPath;
|
|
671
1299
|
let reportSidecarPath;
|
|
1300
|
+
let auditSidecarPath;
|
|
672
1301
|
try {
|
|
673
1302
|
if (context.cwd && context.runId && context.taskId) {
|
|
674
1303
|
const taskDir = join(
|
|
@@ -682,8 +1311,10 @@ export default async function renderExecutive({ sources, options = {}, context =
|
|
|
682
1311
|
await mkdir(taskDir, { recursive: true });
|
|
683
1312
|
executiveSidecarPath = join(taskDir, "executive.md");
|
|
684
1313
|
reportSidecarPath = join(taskDir, "report.md");
|
|
1314
|
+
auditSidecarPath = join(taskDir, "audit.md");
|
|
685
1315
|
await writeFile(executiveSidecarPath, `${markdown}\n`, "utf8");
|
|
686
1316
|
await writeFile(reportSidecarPath, `${markdown}\n`, "utf8");
|
|
1317
|
+
await writeFile(auditSidecarPath, `${auditMarkdown}\n`, "utf8");
|
|
687
1318
|
}
|
|
688
1319
|
} catch {
|
|
689
1320
|
// Sidecars are non-authoritative; keep control output deterministic.
|
|
@@ -696,6 +1327,7 @@ export default async function renderExecutive({ sources, options = {}, context =
|
|
|
696
1327
|
renderMode: "evidence-backed-report",
|
|
697
1328
|
executiveMarkdown: markdown,
|
|
698
1329
|
reportMarkdown: markdown,
|
|
1330
|
+
auditMarkdown,
|
|
699
1331
|
wordCount,
|
|
700
1332
|
sourceUrlCount,
|
|
701
1333
|
totalSourceUrlCount: rendered.allSourceIndex.length,
|
|
@@ -717,10 +1349,12 @@ export default async function renderExecutive({ sources, options = {}, context =
|
|
|
717
1349
|
maxGaps: opts.maxGaps,
|
|
718
1350
|
truncated,
|
|
719
1351
|
truncatedWithOpenGaps,
|
|
1352
|
+
serializationArtifact,
|
|
720
1353
|
passed,
|
|
721
1354
|
},
|
|
722
|
-
auditArtifact: "final-audit.control.json",
|
|
1355
|
+
auditArtifact: auditSidecarPath ? "audit.md" : "final-audit.control.json",
|
|
723
1356
|
...(executiveSidecarPath ? { sidecarPath: "executive.md" } : {}),
|
|
724
1357
|
...(reportSidecarPath ? { reportSidecarPath: "report.md" } : {}),
|
|
1358
|
+
...(auditSidecarPath ? { auditSidecarPath: "audit.md" } : {}),
|
|
725
1359
|
};
|
|
726
1360
|
}
|