@agwab/pi-workflow 0.2.1 → 0.3.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/dist/compiler.js +6 -8
- package/dist/dynamic-decision.d.ts +0 -1
- package/dist/dynamic-decision.js +0 -7
- package/dist/dynamic-profiles.d.ts +0 -1
- package/dist/dynamic-profiles.js +0 -3
- package/dist/engine-run-graph.d.ts +1 -0
- package/dist/engine-run-graph.js +142 -2
- package/dist/engine.d.ts +5 -0
- package/dist/engine.js +112 -27
- package/dist/extension.d.ts +2 -1
- package/dist/extension.js +27 -6
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/store.js +55 -11
- package/dist/subagent-backend.js +155 -29
- package/dist/types.d.ts +6 -0
- package/dist/workflow-runtime.js +10 -1
- package/dist/workflow-view.js +3 -1
- package/dist/workflow-web-source-extension.js +167 -48
- package/dist/workflow-web-source.d.ts +2 -1
- package/dist/workflow-web-source.js +84 -19
- package/node_modules/@agwab/pi-subagent/README.md +3 -3
- package/node_modules/@agwab/pi-subagent/api.mjs +1 -0
- package/node_modules/@agwab/pi-subagent/docs/usage.md +63 -12
- package/node_modules/@agwab/pi-subagent/package.json +2 -2
- package/node_modules/@agwab/pi-subagent/src/api.ts +54 -1
- package/node_modules/@agwab/pi-subagent/src/artifacts/registry.ts +9 -4
- package/node_modules/@agwab/pi-subagent/src/artifacts/result.ts +8 -0
- package/node_modules/@agwab/pi-subagent/src/core/constants.ts +9 -0
- package/node_modules/@agwab/pi-subagent/src/core/validation.ts +21 -0
- package/node_modules/@agwab/pi-subagent/src/index.ts +995 -573
- package/node_modules/@agwab/pi-subagent/src/orchestrate/async.ts +279 -156
- package/node_modules/@agwab/pi-subagent/src/orchestrate/interrupt.ts +165 -89
- package/node_modules/@agwab/pi-subagent/src/orchestrate/reconcile.ts +111 -65
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run-ref.ts +219 -0
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run.ts +88 -8
- package/node_modules/@agwab/pi-subagent/src/orchestrate/status.ts +614 -298
- package/node_modules/@agwab/pi-subagent/src/panel.ts +1352 -560
- package/node_modules/@agwab/pi-subagent/src/runners/headless-model.ts +53 -5
- package/node_modules/@agwab/pi-subagent/src/runners/tmux.ts +13 -6
- package/package.json +2 -2
- package/src/compiler.ts +14 -9
- package/src/dynamic-decision.ts +0 -11
- package/src/dynamic-profiles.ts +0 -4
- package/src/engine-run-graph.ts +185 -2
- package/src/engine.ts +145 -24
- package/src/extension.ts +33 -4
- package/src/index.ts +3 -1
- package/src/store.ts +74 -11
- package/src/subagent-backend.ts +201 -28
- package/src/types.ts +6 -0
- package/src/workflow-runtime.ts +18 -2
- package/src/workflow-view.ts +2 -1
- package/src/workflow-web-source-extension.ts +621 -228
- package/src/workflow-web-source.ts +118 -28
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +56 -16
- package/workflows/deep-research/helpers/final-audit-packet.mjs +1 -4
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +1 -1
- package/workflows/deep-research/helpers/render-executive.mjs +8 -21
- package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +89 -15
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +0 -1
- package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +4 -1
- package/workflows/impact-review/spec.json +3 -3
- package/workflows/spec-review/helpers/spec-review-pipeline.mjs +1 -8
- package/dist/dynamic-loader.d.ts +0 -25
- package/dist/dynamic-loader.js +0 -13
- package/src/dynamic-loader.ts +0 -49
- package/workflows/impact-review/schemas/docs-release-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/security-performance-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/state-data-impact-control.schema.json +0 -42
|
@@ -98,7 +98,7 @@ export interface WorkflowWebSourceReadRequest {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
export interface WorkflowWebSourceReadResult {
|
|
101
|
-
status: "matched" | "not_found";
|
|
101
|
+
status: "matched" | "truncated" | "not_found";
|
|
102
102
|
matchType?: "exact" | "normalized" | "terms";
|
|
103
103
|
quote?: string;
|
|
104
104
|
startOffset?: number;
|
|
@@ -108,6 +108,7 @@ export interface WorkflowWebSourceReadResult {
|
|
|
108
108
|
missingTerms?: string[];
|
|
109
109
|
coverageRatio?: number;
|
|
110
110
|
candidateOnly?: boolean;
|
|
111
|
+
truncated?: boolean;
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
export interface WorkflowWebSourceCard {
|
|
@@ -791,6 +792,8 @@ function snippetForTerms(options: {
|
|
|
791
792
|
const candidates: Array<{
|
|
792
793
|
start: number;
|
|
793
794
|
end: number;
|
|
795
|
+
anchorStart: number;
|
|
796
|
+
anchorEnd: number;
|
|
794
797
|
matchedTerms: string[];
|
|
795
798
|
missingTerms: string[];
|
|
796
799
|
score: number;
|
|
@@ -822,23 +825,34 @@ function snippetForTerms(options: {
|
|
|
822
825
|
if (right.score !== left.score) return right.score - left.score;
|
|
823
826
|
return right.matchedTerms.length - left.matchedTerms.length;
|
|
824
827
|
})[0]!;
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
options.maxChars,
|
|
830
|
-
|
|
828
|
+
const consumed = consumeAnchoredSnippet({
|
|
829
|
+
text: options.text,
|
|
830
|
+
anchorStart: best.anchorStart,
|
|
831
|
+
anchorEnd: best.anchorEnd,
|
|
832
|
+
maxChars: options.maxChars,
|
|
833
|
+
budget: options.budget,
|
|
834
|
+
});
|
|
835
|
+
const returnedWindowNorm = normalizeForSearch(
|
|
836
|
+
options.text.slice(consumed.sourceStart, consumed.sourceEnd),
|
|
837
|
+
).normalized;
|
|
838
|
+
const matchedTerms = needles
|
|
839
|
+
.filter((term) => returnedWindowNorm.includes(term.normalized))
|
|
840
|
+
.map((term) => term.raw);
|
|
841
|
+
const missingTerms = needles
|
|
842
|
+
.filter((term) => !returnedWindowNorm.includes(term.normalized))
|
|
843
|
+
.map((term) => term.raw);
|
|
831
844
|
return {
|
|
832
|
-
status:
|
|
845
|
+
status: consumed.status,
|
|
833
846
|
matchType: "terms",
|
|
834
|
-
quote: consumed.
|
|
835
|
-
startOffset:
|
|
836
|
-
endOffset:
|
|
837
|
-
visibleChars: consumed.
|
|
838
|
-
matchedTerms
|
|
839
|
-
missingTerms
|
|
840
|
-
coverageRatio:
|
|
847
|
+
quote: consumed.quote || undefined,
|
|
848
|
+
startOffset: consumed.sourceStart,
|
|
849
|
+
endOffset: consumed.sourceEnd,
|
|
850
|
+
visibleChars: consumed.visibleChars,
|
|
851
|
+
matchedTerms,
|
|
852
|
+
missingTerms,
|
|
853
|
+
coverageRatio: matchedTerms.length / Math.max(1, needles.length),
|
|
841
854
|
candidateOnly: true,
|
|
855
|
+
truncated: consumed.truncated || undefined,
|
|
842
856
|
};
|
|
843
857
|
}
|
|
844
858
|
|
|
@@ -854,6 +868,8 @@ function scoreTermWindow(
|
|
|
854
868
|
matchedTerms: string[];
|
|
855
869
|
missingTerms: string[];
|
|
856
870
|
score: number;
|
|
871
|
+
anchorStart: number;
|
|
872
|
+
anchorEnd: number;
|
|
857
873
|
} {
|
|
858
874
|
const center = Math.floor((matchStart + matchEnd) / 2);
|
|
859
875
|
const start = Math.max(0, center - Math.floor(maxChars / 2));
|
|
@@ -874,6 +890,8 @@ function scoreTermWindow(
|
|
|
874
890
|
return {
|
|
875
891
|
start,
|
|
876
892
|
end,
|
|
893
|
+
anchorStart: matchStart,
|
|
894
|
+
anchorEnd: matchEnd,
|
|
877
895
|
matchedTerms,
|
|
878
896
|
missingTerms,
|
|
879
897
|
score: matchedTerms.length * 1_000 + occurrenceScore,
|
|
@@ -963,27 +981,99 @@ function snippetForMatch(options: {
|
|
|
963
981
|
maxChars: number;
|
|
964
982
|
budget: WorkflowWebVisibleBudget;
|
|
965
983
|
}): WorkflowWebSourceReadResult {
|
|
966
|
-
const
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
options.
|
|
972
|
-
|
|
984
|
+
const consumed = consumeAnchoredSnippet({
|
|
985
|
+
text: options.text,
|
|
986
|
+
anchorStart: options.start,
|
|
987
|
+
anchorEnd: options.end,
|
|
988
|
+
maxChars: options.maxChars,
|
|
989
|
+
budget: options.budget,
|
|
990
|
+
});
|
|
991
|
+
return {
|
|
992
|
+
status: consumed.status,
|
|
993
|
+
matchType: options.matchType,
|
|
994
|
+
quote: consumed.quote || undefined,
|
|
995
|
+
startOffset: options.start,
|
|
996
|
+
endOffset: options.end,
|
|
997
|
+
visibleChars: consumed.visibleChars,
|
|
998
|
+
truncated: consumed.truncated || undefined,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
type AnchoredSnippetResult = {
|
|
1003
|
+
status: "matched" | "truncated";
|
|
1004
|
+
quote: string;
|
|
1005
|
+
visibleChars: number;
|
|
1006
|
+
sourceStart: number;
|
|
1007
|
+
sourceEnd: number;
|
|
1008
|
+
truncated: boolean;
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
function consumeAnchoredSnippet(options: {
|
|
1012
|
+
text: string;
|
|
1013
|
+
anchorStart: number;
|
|
1014
|
+
anchorEnd: number;
|
|
1015
|
+
maxChars: number;
|
|
1016
|
+
budget: WorkflowWebVisibleBudget;
|
|
1017
|
+
}): AnchoredSnippetResult {
|
|
1018
|
+
const maxChars = Math.max(0, Math.floor(options.maxChars));
|
|
1019
|
+
const remainingBefore = Math.max(
|
|
1020
|
+
0,
|
|
1021
|
+
options.budget.limit - options.budget.used,
|
|
1022
|
+
);
|
|
1023
|
+
const visibleLimit = Math.max(0, Math.min(maxChars, remainingBefore));
|
|
1024
|
+
const anchorStart = Math.max(
|
|
1025
|
+
0,
|
|
1026
|
+
Math.min(options.text.length, Math.floor(options.anchorStart)),
|
|
1027
|
+
);
|
|
1028
|
+
const anchorEnd = Math.max(
|
|
1029
|
+
anchorStart,
|
|
1030
|
+
Math.min(options.text.length, Math.floor(options.anchorEnd)),
|
|
973
1031
|
);
|
|
974
|
-
const
|
|
1032
|
+
const anchorLength = Math.max(0, anchorEnd - anchorStart);
|
|
1033
|
+
if (visibleLimit <= 0) {
|
|
1034
|
+
return {
|
|
1035
|
+
status: "truncated",
|
|
1036
|
+
quote: "",
|
|
1037
|
+
visibleChars: 0,
|
|
1038
|
+
sourceStart: anchorStart,
|
|
1039
|
+
sourceEnd: anchorStart,
|
|
1040
|
+
truncated: true,
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
let sourceStart: number;
|
|
1045
|
+
let sourceEnd: number;
|
|
1046
|
+
let status: "matched" | "truncated" = "matched";
|
|
1047
|
+
if (anchorLength > visibleLimit) {
|
|
1048
|
+
sourceStart = anchorStart;
|
|
1049
|
+
sourceEnd = Math.min(options.text.length, sourceStart + visibleLimit);
|
|
1050
|
+
status = "truncated";
|
|
1051
|
+
} else {
|
|
1052
|
+
const slack = Math.max(0, visibleLimit - anchorLength);
|
|
1053
|
+
sourceStart = Math.max(0, anchorStart - Math.floor(slack / 2));
|
|
1054
|
+
sourceEnd = Math.min(options.text.length, sourceStart + visibleLimit);
|
|
1055
|
+
if (sourceEnd < anchorEnd) {
|
|
1056
|
+
sourceEnd = anchorEnd;
|
|
1057
|
+
sourceStart = Math.max(0, sourceEnd - visibleLimit);
|
|
1058
|
+
} else if (sourceEnd === options.text.length) {
|
|
1059
|
+
sourceStart = Math.max(0, sourceEnd - visibleLimit);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
const raw = redactInlineSecrets(options.text.slice(sourceStart, sourceEnd));
|
|
975
1064
|
const consumed = consumeWorkflowWebVisibleBudget(
|
|
976
1065
|
options.budget,
|
|
977
1066
|
raw,
|
|
978
|
-
|
|
1067
|
+
visibleLimit,
|
|
979
1068
|
);
|
|
1069
|
+
const truncated = status === "truncated" || consumed.truncated;
|
|
980
1070
|
return {
|
|
981
|
-
status
|
|
982
|
-
matchType: options.matchType,
|
|
1071
|
+
status,
|
|
983
1072
|
quote: consumed.text,
|
|
984
|
-
startOffset: options.start,
|
|
985
|
-
endOffset: options.end,
|
|
986
1073
|
visibleChars: consumed.text.length,
|
|
1074
|
+
sourceStart,
|
|
1075
|
+
sourceEnd,
|
|
1076
|
+
truncated,
|
|
987
1077
|
};
|
|
988
1078
|
}
|
|
989
1079
|
|
|
@@ -82,6 +82,38 @@ function collectEvidenceRefs(claim) {
|
|
|
82
82
|
return refs;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
function addLocalEvidenceRef(refs, value) {
|
|
86
|
+
if (typeof value !== "string") return;
|
|
87
|
+
const text = value.trim();
|
|
88
|
+
if (!text || /^https?:\/\//i.test(text) || isWorkflowSourceRef(text)) return;
|
|
89
|
+
if (looksLikeLocalSourceRef(text)) refs.add(text);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function collectLocalEvidenceRefs(claim) {
|
|
93
|
+
const refs = new Set();
|
|
94
|
+
if (!claim || typeof claim !== "object") return refs;
|
|
95
|
+
for (const key of ["file", "path", "repoPath", "localPath", "sourceRef"]) {
|
|
96
|
+
addLocalEvidenceRef(refs, claim[key]);
|
|
97
|
+
}
|
|
98
|
+
for (const value of Array.isArray(claim.sourceRefs) ? claim.sourceRefs : []) {
|
|
99
|
+
addLocalEvidenceRef(refs, value);
|
|
100
|
+
}
|
|
101
|
+
for (const row of Array.isArray(claim.evidence) ? claim.evidence : []) {
|
|
102
|
+
if (!row || typeof row !== "object") continue;
|
|
103
|
+
for (const key of [
|
|
104
|
+
"file",
|
|
105
|
+
"path",
|
|
106
|
+
"repoPath",
|
|
107
|
+
"localPath",
|
|
108
|
+
"source",
|
|
109
|
+
"sourceRef",
|
|
110
|
+
]) {
|
|
111
|
+
addLocalEvidenceRef(refs, row[key]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return refs;
|
|
115
|
+
}
|
|
116
|
+
|
|
85
117
|
function collectWorkflowSourceRefs(value, refs = new Set()) {
|
|
86
118
|
if (typeof value === "string") {
|
|
87
119
|
for (const match of value.matchAll(/\bwsrc_[a-f0-9]{32}\b/g))
|
|
@@ -127,9 +159,11 @@ function canonicalUrlKeys(value) {
|
|
|
127
159
|
url.hash = "";
|
|
128
160
|
const serialized = stripCitationUrlPunctuation(url.toString());
|
|
129
161
|
keys.add(serialized);
|
|
162
|
+
addNpmDocsVersionAgnosticKey(keys, url);
|
|
130
163
|
if (url.pathname !== "/" && url.pathname.endsWith("/")) {
|
|
131
164
|
url.pathname = url.pathname.replace(/\/+$/u, "");
|
|
132
165
|
keys.add(stripCitationUrlPunctuation(url.toString()));
|
|
166
|
+
addNpmDocsVersionAgnosticKey(keys, url);
|
|
133
167
|
}
|
|
134
168
|
} catch {
|
|
135
169
|
// Keep the trimmed raw URL key only; malformed strings should not throw from
|
|
@@ -138,6 +172,17 @@ function canonicalUrlKeys(value) {
|
|
|
138
172
|
return [...keys].filter(Boolean);
|
|
139
173
|
}
|
|
140
174
|
|
|
175
|
+
function addNpmDocsVersionAgnosticKey(keys, url) {
|
|
176
|
+
if (url.hostname !== "docs.npmjs.com") return;
|
|
177
|
+
if (!/^\/cli\/(?:v\d+\/)?using-npm\//u.test(url.pathname)) return;
|
|
178
|
+
const versionless = new URL(url.toString());
|
|
179
|
+
versionless.pathname = versionless.pathname.replace(
|
|
180
|
+
/^\/cli\/v\d+\//u,
|
|
181
|
+
"/cli/",
|
|
182
|
+
);
|
|
183
|
+
keys.add(stripCitationUrlPunctuation(versionless.toString()));
|
|
184
|
+
}
|
|
185
|
+
|
|
141
186
|
function addUrlSourceRef(urlToSourceRef, url, sourceRef) {
|
|
142
187
|
if (!isWorkflowSourceRef(sourceRef)) return;
|
|
143
188
|
for (const key of canonicalUrlKeys(url)) {
|
|
@@ -152,13 +197,6 @@ function buildUrlSourceRefLookup(normalizeInputPacket) {
|
|
|
152
197
|
if (!source || typeof source !== "object") continue;
|
|
153
198
|
addUrlSourceRef(urlToSourceRef, source.url, source.sourceRef);
|
|
154
199
|
}
|
|
155
|
-
const sourceRefIndex = asArray(
|
|
156
|
-
normalizeInputPacket?.packet?.research?.sourceRefIndex,
|
|
157
|
-
);
|
|
158
|
-
for (const source of sourceRefIndex) {
|
|
159
|
-
if (!source || typeof source !== "object") continue;
|
|
160
|
-
addUrlSourceRef(urlToSourceRef, source.url, source.sourceRef);
|
|
161
|
-
}
|
|
162
200
|
return urlToSourceRef;
|
|
163
201
|
}
|
|
164
202
|
|
|
@@ -241,19 +279,19 @@ function strongEvidenceIssue(claim) {
|
|
|
241
279
|
|
|
242
280
|
function hasExactQuantitativeClaim(value) {
|
|
243
281
|
const text = JSON.stringify(value ?? "");
|
|
244
|
-
return /\b\d+(?:\.\d+)?\s*(
|
|
282
|
+
return /\b\d+(?:\.\d+)?\s*(?:(?:%|×|\$|n\s*=)|(?:percent|ms|s|sec|seconds|minutes|hours|x|usd|k|m|b|tokens?|users?|samples?)\b)/i.test(
|
|
245
283
|
text,
|
|
246
284
|
);
|
|
247
285
|
}
|
|
248
286
|
|
|
249
287
|
function verdictOf(claim) {
|
|
250
|
-
|
|
288
|
+
const status =
|
|
251
289
|
claim?.status ??
|
|
252
290
|
claim?.verdict ??
|
|
253
291
|
claim?.verdictDigest?.status ??
|
|
254
292
|
claim?.verdictDigest?.verdict ??
|
|
255
|
-
"unverified"
|
|
256
|
-
);
|
|
293
|
+
"unverified";
|
|
294
|
+
return canonicalVerifierStatus(status);
|
|
257
295
|
}
|
|
258
296
|
|
|
259
297
|
function withVerdict(claim, verdict, reason, details = {}) {
|
|
@@ -416,9 +454,7 @@ function findSource(sources, stageId) {
|
|
|
416
454
|
export default async function claimEvidenceGate({ sources, options = {} }) {
|
|
417
455
|
const plan = findSource(sources, "plan");
|
|
418
456
|
const normalizeClaims = findSource(sources, "normalize-claims");
|
|
419
|
-
const sanitizedCandidates =
|
|
420
|
-
findSource(sources, "sanitize-claims") ??
|
|
421
|
-
findSource(sources, "sanitize-verification-candidates");
|
|
457
|
+
const sanitizedCandidates = findSource(sources, "sanitize-claims");
|
|
422
458
|
const normalized = sanitizedCandidates ?? normalizeClaims;
|
|
423
459
|
const normalizeInputPacket = findSource(sources, "normalize-input-packet");
|
|
424
460
|
const urlToSourceRef = buildUrlSourceRefLookup(normalizeInputPacket);
|
|
@@ -474,8 +510,7 @@ export default async function claimEvidenceGate({ sources, options = {} }) {
|
|
|
474
510
|
!specId.startsWith("plan") &&
|
|
475
511
|
!specId.startsWith("normalize-claims") &&
|
|
476
512
|
!specId.startsWith("normalize-input-packet") &&
|
|
477
|
-
!specId.startsWith("sanitize-claims")
|
|
478
|
-
!specId.startsWith("sanitize-verification-candidates"),
|
|
513
|
+
!specId.startsWith("sanitize-claims"),
|
|
479
514
|
)
|
|
480
515
|
.flatMap(([sourceId, source]) =>
|
|
481
516
|
asArray(source).map((claim, index) => ({ sourceId, claim, index })),
|
|
@@ -558,6 +593,10 @@ export default async function claimEvidenceGate({ sources, options = {} }) {
|
|
|
558
593
|
if (!claim || typeof claim !== "object") return;
|
|
559
594
|
gateSummary.total += 1;
|
|
560
595
|
const evidenceRefs = [...collectEvidenceRefs(claim)];
|
|
596
|
+
const localEvidenceRefs = new Set([
|
|
597
|
+
...collectLocalEvidenceRefs(claim),
|
|
598
|
+
...collectLocalEvidenceRefs(candidate),
|
|
599
|
+
]);
|
|
561
600
|
const workflowSourceRefs = new Set([...collectWorkflowSourceRefs(claim)]);
|
|
562
601
|
const exactQuantitative = hasExactQuantitativeClaim(claim);
|
|
563
602
|
const fetched = hasFetchedEvidence(claim);
|
|
@@ -627,6 +666,7 @@ export default async function claimEvidenceGate({ sources, options = {} }) {
|
|
|
627
666
|
claimId &&
|
|
628
667
|
candidate &&
|
|
629
668
|
workflowSourceRefs.size === 0 &&
|
|
669
|
+
localEvidenceRefs.size === 0 &&
|
|
630
670
|
httpSourceUrls.length > 0
|
|
631
671
|
) {
|
|
632
672
|
const failure = {
|
|
@@ -252,10 +252,7 @@ function buildSynthesisInput({
|
|
|
252
252
|
export default async function finalAuditPacket({ sources }) {
|
|
253
253
|
const plan = asObject(findSource(sources, "plan"));
|
|
254
254
|
const normalizeClaims = asObject(findSource(sources, "normalize-claims"));
|
|
255
|
-
const sanitizedCandidates = asObject(
|
|
256
|
-
findSource(sources, "sanitize-claims") ??
|
|
257
|
-
findSource(sources, "sanitize-verification-candidates"),
|
|
258
|
-
);
|
|
255
|
+
const sanitizedCandidates = asObject(findSource(sources, "sanitize-claims"));
|
|
259
256
|
const normalized =
|
|
260
257
|
Object.keys(sanitizedCandidates).length > 0
|
|
261
258
|
? sanitizedCandidates
|
|
@@ -221,7 +221,7 @@ function isRequiredOrCriticalSlot(slot) {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
function looksQuantitative(text) {
|
|
224
|
-
return /\b\d+(?:\.\d+)?\s*(
|
|
224
|
+
return /\b\d+(?:\.\d+)?\s*(?:(?:%|×|\$|n\s*=)|(?:percent|ms|s|sec|seconds|minutes|hours|x|usd|k|m|b|tokens?|users?|samples?|gb|mb|tb|requests?|qps|rps|per\s+month|\/month)\b)/i.test(
|
|
225
225
|
String(text ?? ""),
|
|
226
226
|
);
|
|
227
227
|
}
|
|
@@ -468,7 +468,14 @@ function packetGapRows(packet) {
|
|
|
468
468
|
kind: "Coverage gap",
|
|
469
469
|
...gap,
|
|
470
470
|
}));
|
|
471
|
-
|
|
471
|
+
const sourceRefJoinFailures = asArray(packet?.sourceRefJoinFailures).map(
|
|
472
|
+
(gap, index) => ({
|
|
473
|
+
id: gapIdOf(gap) || numberedId("gap-source-ref", index),
|
|
474
|
+
kind: "Source reference gap",
|
|
475
|
+
...gap,
|
|
476
|
+
}),
|
|
477
|
+
);
|
|
478
|
+
return [...remaining, ...coverage, ...sourceRefJoinFailures];
|
|
472
479
|
}
|
|
473
480
|
|
|
474
481
|
function rowsForIds(ids, rowById, warnings, label) {
|
|
@@ -987,11 +994,7 @@ function renderAuditSummary(report, claimSummary, slots) {
|
|
|
987
994
|
|
|
988
995
|
function renderWarnings(sectionCounts) {
|
|
989
996
|
const checks = [
|
|
990
|
-
["findings", "renderedFindings", "findings"],
|
|
991
|
-
["recommendations", "renderedRecommendations", "recommendations"],
|
|
992
|
-
["actionItems", "renderedActionItems", "action items"],
|
|
993
997
|
["caveatsAndGaps", "renderedCaveatsAndGaps", "caveats/gaps"],
|
|
994
|
-
["factSlots", "renderedFactSlots", "fact slots"],
|
|
995
998
|
["sourceUrls", "renderedSourceUrls", "source URLs"],
|
|
996
999
|
];
|
|
997
1000
|
return checks
|
|
@@ -1261,15 +1264,6 @@ export default async function renderExecutive({
|
|
|
1261
1264
|
maxUrls: Number.isFinite(Number(options.maxUrls))
|
|
1262
1265
|
? Math.max(0, Number(options.maxUrls))
|
|
1263
1266
|
: Infinity,
|
|
1264
|
-
maxFindings: Number.isFinite(Number(options.maxFindings))
|
|
1265
|
-
? Math.max(0, Number(options.maxFindings))
|
|
1266
|
-
: undefined,
|
|
1267
|
-
maxRecommendations: Number.isFinite(Number(options.maxRecommendations))
|
|
1268
|
-
? Math.max(0, Number(options.maxRecommendations))
|
|
1269
|
-
: undefined,
|
|
1270
|
-
maxGaps: Number.isFinite(Number(options.maxGaps))
|
|
1271
|
-
? Math.max(0, Number(options.maxGaps))
|
|
1272
|
-
: undefined,
|
|
1273
1267
|
};
|
|
1274
1268
|
const rendered = renderResearchMarkdown(control, auditPacket, opts);
|
|
1275
1269
|
let markdown = rendered.markdown;
|
|
@@ -1296,7 +1290,6 @@ export default async function renderExecutive({
|
|
|
1296
1290
|
!serializationArtifact;
|
|
1297
1291
|
|
|
1298
1292
|
let executiveSidecarPath;
|
|
1299
|
-
let reportSidecarPath;
|
|
1300
1293
|
let auditSidecarPath;
|
|
1301
1294
|
try {
|
|
1302
1295
|
if (context.cwd && context.runId && context.taskId) {
|
|
@@ -1310,10 +1303,8 @@ export default async function renderExecutive({
|
|
|
1310
1303
|
);
|
|
1311
1304
|
await mkdir(taskDir, { recursive: true });
|
|
1312
1305
|
executiveSidecarPath = join(taskDir, "executive.md");
|
|
1313
|
-
reportSidecarPath = join(taskDir, "report.md");
|
|
1314
1306
|
auditSidecarPath = join(taskDir, "audit.md");
|
|
1315
1307
|
await writeFile(executiveSidecarPath, `${markdown}\n`, "utf8");
|
|
1316
|
-
await writeFile(reportSidecarPath, `${markdown}\n`, "utf8");
|
|
1317
1308
|
await writeFile(auditSidecarPath, `${auditMarkdown}\n`, "utf8");
|
|
1318
1309
|
}
|
|
1319
1310
|
} catch {
|
|
@@ -1344,9 +1335,6 @@ export default async function renderExecutive({
|
|
|
1344
1335
|
renderedAllStructuredItems,
|
|
1345
1336
|
maxWords: Number.isFinite(opts.maxWords) ? opts.maxWords : null,
|
|
1346
1337
|
maxUrls: Number.isFinite(opts.maxUrls) ? opts.maxUrls : null,
|
|
1347
|
-
maxFindings: opts.maxFindings,
|
|
1348
|
-
maxRecommendations: opts.maxRecommendations,
|
|
1349
|
-
maxGaps: opts.maxGaps,
|
|
1350
1338
|
truncated,
|
|
1351
1339
|
truncatedWithOpenGaps,
|
|
1352
1340
|
serializationArtifact,
|
|
@@ -1354,7 +1342,6 @@ export default async function renderExecutive({
|
|
|
1354
1342
|
},
|
|
1355
1343
|
auditArtifact: auditSidecarPath ? "audit.md" : "final-audit.control.json",
|
|
1356
1344
|
...(executiveSidecarPath ? { sidecarPath: "executive.md" } : {}),
|
|
1357
|
-
...(reportSidecarPath ? { reportSidecarPath: "report.md" } : {}),
|
|
1358
1345
|
...(auditSidecarPath ? { auditSidecarPath: "audit.md" } : {}),
|
|
1359
1346
|
};
|
|
1360
1347
|
}
|
|
@@ -89,12 +89,55 @@ function matchesAny(text, patterns) {
|
|
|
89
89
|
return patterns.some((pattern) => pattern.test(text));
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
const WORD_TOKEN_RE =
|
|
93
|
+
/[\p{Letter}\p{Number}][\p{Letter}\p{Number}\p{Mark}_-]*/gu;
|
|
94
|
+
const ASCII_TOKEN_RE = /^[a-z0-9][a-z0-9_-]{2,}$/iu;
|
|
95
|
+
const ASCII_RUN_RE = /[a-z0-9][a-z0-9_-]{2,}/giu;
|
|
96
|
+
const CJK_RUN_RE =
|
|
97
|
+
/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+/gu;
|
|
98
|
+
const ASCII_CHAR_RE = /[a-z0-9]/iu;
|
|
99
|
+
|
|
100
|
+
function addCjkRunTokens(tokens, run) {
|
|
101
|
+
const chars = [...run];
|
|
102
|
+
if (chars.length === 0) return;
|
|
103
|
+
if (chars.length === 1) {
|
|
104
|
+
tokens.add(chars[0]);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
tokens.add(run);
|
|
108
|
+
for (const size of [2, 3]) {
|
|
109
|
+
if (chars.length < size) continue;
|
|
110
|
+
for (let index = 0; index <= chars.length - size; index += 1) {
|
|
111
|
+
tokens.add(chars.slice(index, index + size).join(""));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
92
116
|
function tokenSet(value) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
)
|
|
117
|
+
const tokens = new Set();
|
|
118
|
+
const normalized = String(value ?? "")
|
|
119
|
+
.normalize("NFKC")
|
|
120
|
+
.toLocaleLowerCase();
|
|
121
|
+
for (const match of normalized.matchAll(WORD_TOKEN_RE)) {
|
|
122
|
+
const token = match[0].replace(/^[_-]+|[_-]+$/gu, "");
|
|
123
|
+
if (!token) continue;
|
|
124
|
+
for (const asciiMatch of token.matchAll(ASCII_RUN_RE)) {
|
|
125
|
+
tokens.add(asciiMatch[0]);
|
|
126
|
+
}
|
|
127
|
+
if (ASCII_TOKEN_RE.test(token)) continue;
|
|
128
|
+
let cjkMatched = false;
|
|
129
|
+
for (const cjkMatch of token.matchAll(CJK_RUN_RE)) {
|
|
130
|
+
cjkMatched = true;
|
|
131
|
+
addCjkRunTokens(tokens, cjkMatch[0]);
|
|
132
|
+
}
|
|
133
|
+
if (!cjkMatched && [...token].length >= 3) tokens.add(token);
|
|
134
|
+
}
|
|
135
|
+
return tokens;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function hasAsciiToken(tokens) {
|
|
139
|
+
for (const token of tokens) if (ASCII_CHAR_RE.test(token)) return true;
|
|
140
|
+
return false;
|
|
98
141
|
}
|
|
99
142
|
|
|
100
143
|
function setIntersectionCount(left, right) {
|
|
@@ -235,10 +278,15 @@ function evidenceHintsForCandidate(candidate, hintRows) {
|
|
|
235
278
|
candidateSlots,
|
|
236
279
|
);
|
|
237
280
|
const tokenHits = setIntersectionCount(row._tokens, candidateTokens);
|
|
281
|
+
const unicodeSlotOnlyFallback =
|
|
282
|
+
slotHits > 0 &&
|
|
283
|
+
tokenHits < 2 &&
|
|
284
|
+
!hasAsciiToken(candidateTokens) &&
|
|
285
|
+
!hasAsciiToken(row._tokens);
|
|
238
286
|
if (slotHits === 0 && tokenHits < 2) continue;
|
|
239
287
|
const score =
|
|
240
288
|
refHits * 6 + urlHits * 5 + slotHits * 2 + Math.min(tokenHits, 5);
|
|
241
|
-
if (score < 7) continue;
|
|
289
|
+
if (score < 7 && !unicodeSlotOnlyFallback) continue;
|
|
242
290
|
scored.push({ score, row });
|
|
243
291
|
}
|
|
244
292
|
scored.sort((left, right) => right.score - left.score);
|
|
@@ -376,11 +424,14 @@ function rewrittenCandidate(candidate, reasons, hints, urlToSourceRef) {
|
|
|
376
424
|
if (!hint) return null;
|
|
377
425
|
const replacement = stringOf(hint.value) || stringOf(hint.quote);
|
|
378
426
|
if (!replacement || replacement === claimText(candidate)) return null;
|
|
427
|
+
const refs = backfillSourceRefs(candidate, [hint], urlToSourceRef);
|
|
428
|
+
if (refs.length === 0 && localEvidenceRefs(candidate).length === 0)
|
|
429
|
+
return null;
|
|
379
430
|
return {
|
|
380
431
|
...candidate,
|
|
381
432
|
originalClaim: claimText(candidate),
|
|
382
433
|
claim: replacement,
|
|
383
|
-
sourceRefs:
|
|
434
|
+
sourceRefs: refs,
|
|
384
435
|
sourceUrls: hint.url ? [hint.url] : sourceUrls(candidate),
|
|
385
436
|
sanitizerRewriteReasons: rewriteReasons,
|
|
386
437
|
reasonToVerify: `Deterministically rewritten to a source-backed atom from ${hint.sourceTitleOrPublisher ?? hint.url ?? hint.sourceRef ?? "source evidence"}.`,
|
|
@@ -399,6 +450,16 @@ function sanitizedCandidate(candidate, hints, urlToSourceRef) {
|
|
|
399
450
|
};
|
|
400
451
|
}
|
|
401
452
|
|
|
453
|
+
function withoutSanitizerGapReason(value) {
|
|
454
|
+
return stringOf(value)
|
|
455
|
+
.split(";")
|
|
456
|
+
.map((part) => part.trim())
|
|
457
|
+
.filter(
|
|
458
|
+
(part) => part && !part.startsWith("sanitized verifier candidates:"),
|
|
459
|
+
)
|
|
460
|
+
.join("; ");
|
|
461
|
+
}
|
|
462
|
+
|
|
402
463
|
function adjustFactSlotCoverage(rows, demotedBySlot, keptIds) {
|
|
403
464
|
return asArray(rows).map((row) => {
|
|
404
465
|
const slot = { ...asObject(row) };
|
|
@@ -484,6 +545,7 @@ export default async function sanitizeVerificationCandidates({ sources }) {
|
|
|
484
545
|
const webUrlOnlyDemotedIds = [];
|
|
485
546
|
const promotedCandidateIds = [];
|
|
486
547
|
const promotedBySlot = new Map();
|
|
548
|
+
const promotedPreservedClaims = new Set();
|
|
487
549
|
const retainedCandidates = [];
|
|
488
550
|
for (const [index, candidate] of keptCandidates.entries()) {
|
|
489
551
|
const hasRefs = sourceRefs(candidate).length > 0;
|
|
@@ -550,6 +612,7 @@ export default async function sanitizeVerificationCandidates({ sources }) {
|
|
|
550
612
|
for (const entry of promotable.slice(0, webUrlOnlyDemotedIds.length)) {
|
|
551
613
|
takenIds.add(entry.id);
|
|
552
614
|
promotedCandidateIds.push(entry.id);
|
|
615
|
+
promotedPreservedClaims.add(entry.preserved);
|
|
553
616
|
for (const slotId of entry.slots) {
|
|
554
617
|
const list = promotedBySlot.get(slotId) ?? [];
|
|
555
618
|
list.push(entry.id);
|
|
@@ -584,19 +647,30 @@ export default async function sanitizeVerificationCandidates({ sources }) {
|
|
|
584
647
|
const slotId = stringOf(row.slotId ?? row.id);
|
|
585
648
|
const promoted = promotedBySlot.get(slotId) ?? [];
|
|
586
649
|
if (promoted.length === 0) return row;
|
|
587
|
-
|
|
588
|
-
...row,
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
650
|
+
const verificationCandidateIds = compactStrings(
|
|
651
|
+
[...asArray(row.verificationCandidateIds), ...promoted],
|
|
652
|
+
24,
|
|
653
|
+
);
|
|
654
|
+
const next = { ...row, verificationCandidateIds };
|
|
655
|
+
if (verificationCandidateIds.length > 0) {
|
|
656
|
+
if (next.status === "partial" || next.status === "missing") {
|
|
657
|
+
next.status = "filled";
|
|
658
|
+
}
|
|
659
|
+
const gapReason = withoutSanitizerGapReason(next.gapReason);
|
|
660
|
+
if (gapReason) next.gapReason = gapReason;
|
|
661
|
+
else delete next.gapReason;
|
|
662
|
+
}
|
|
663
|
+
return next;
|
|
594
664
|
});
|
|
665
|
+
const outputPreservedClaims =
|
|
666
|
+
promotedPreservedClaims.size === 0
|
|
667
|
+
? preservedClaims
|
|
668
|
+
: preservedClaims.filter((claim) => !promotedPreservedClaims.has(claim));
|
|
595
669
|
return {
|
|
596
670
|
schema: SCHEMA,
|
|
597
671
|
claimInventory: {
|
|
598
672
|
verificationCandidates: keptCandidates,
|
|
599
|
-
preservedClaims,
|
|
673
|
+
preservedClaims: outputPreservedClaims,
|
|
600
674
|
duplicates: asArray(claimInventory.duplicates),
|
|
601
675
|
},
|
|
602
676
|
factSlotCoverage: factSlotCoverageRows,
|
|
@@ -56,7 +56,6 @@
|
|
|
56
56
|
},
|
|
57
57
|
"auditArtifact": { "type": "string" },
|
|
58
58
|
"sidecarPath": { "type": "string" },
|
|
59
|
-
"reportSidecarPath": { "type": "string" },
|
|
60
59
|
"auditSidecarPath": { "type": "string" },
|
|
61
60
|
"reportMarkdown": { "type": "string" },
|
|
62
61
|
"auditMarkdown": { "type": "string" },
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
],
|
|
93
93
|
"sourcePolicy": "require-success",
|
|
94
94
|
"output": {
|
|
95
|
-
"controlSchema": "./schemas/
|
|
95
|
+
"controlSchema": "./schemas/api-contract-impact-control.schema.json",
|
|
96
96
|
"analysis": {
|
|
97
97
|
"required": true
|
|
98
98
|
},
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
],
|
|
132
132
|
"sourcePolicy": "require-success",
|
|
133
133
|
"output": {
|
|
134
|
-
"controlSchema": "./schemas/
|
|
134
|
+
"controlSchema": "./schemas/api-contract-impact-control.schema.json",
|
|
135
135
|
"analysis": {
|
|
136
136
|
"required": true
|
|
137
137
|
},
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
],
|
|
151
151
|
"sourcePolicy": "require-success",
|
|
152
152
|
"output": {
|
|
153
|
-
"controlSchema": "./schemas/
|
|
153
|
+
"controlSchema": "./schemas/api-contract-impact-control.schema.json",
|
|
154
154
|
"analysis": {
|
|
155
155
|
"required": true
|
|
156
156
|
},
|
|
@@ -169,14 +169,7 @@ export default async function specReviewPipeline({ sources, options }) {
|
|
|
169
169
|
|
|
170
170
|
function findAnalysisOutput(sources) {
|
|
171
171
|
return (
|
|
172
|
-
sources["
|
|
173
|
-
sources["analysis.candidate-findings.main"] ??
|
|
174
|
-
sources["candidate-findings"] ??
|
|
175
|
-
sources["candidate-findings.main"] ??
|
|
176
|
-
Object.entries(sources).find(([key]) =>
|
|
177
|
-
key.includes("candidate-findings"),
|
|
178
|
-
)?.[1] ??
|
|
179
|
-
{}
|
|
172
|
+
sources["candidate-findings"] ?? sources["candidate-findings.main"] ?? {}
|
|
180
173
|
);
|
|
181
174
|
}
|
|
182
175
|
|