@claudexor/review 1.0.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/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/convergence.d.ts +22 -0
- package/dist/convergence.d.ts.map +1 -0
- package/dist/convergence.js +35 -0
- package/dist/convergence.js.map +1 -0
- package/dist/findings.d.ts +22 -0
- package/dist/findings.d.ts.map +1 -0
- package/dist/findings.js +239 -0
- package/dist/findings.js.map +1 -0
- package/dist/gates.d.ts +19 -0
- package/dist/gates.d.ts.map +1 -0
- package/dist/gates.js +72 -0
- package/dist/gates.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/readiness.d.ts +20 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +28 -0
- package/dist/readiness.js.map +1 -0
- package/dist/revalidate.d.ts +22 -0
- package/dist/revalidate.d.ts.map +1 -0
- package/dist/revalidate.js +95 -0
- package/dist/revalidate.js.map +1 -0
- package/dist/reviewEngine.d.ts +85 -0
- package/dist/reviewEngine.d.ts.map +1 -0
- package/dist/reviewEngine.js +1097 -0
- package/dist/reviewEngine.js.map +1 -0
- package/dist/route.d.ts +25 -0
- package/dist/route.d.ts.map +1 -0
- package/dist/route.js +54 -0
- package/dist/route.js.map +1 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 joi-lab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# @claudexor/review
|
|
2
|
+
|
|
3
|
+
Internal package of [Claudexor](https://github.com/razzant/claudexor) — Deterministic gates, cross-family review, finding revalidation, route proof, convergence, readiness-debt.
|
|
4
|
+
|
|
5
|
+
Published as part of the Claudexor toolchain; it follows the monorepo's
|
|
6
|
+
lockstep version and has no separate semver contract. Use the `claudexor`
|
|
7
|
+
CLI (or `@claudexor/cli`) as the supported entry point.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ConvergencePredicate, GateResult, ReviewFinding } from "@claudexor/schema";
|
|
2
|
+
export interface ConvergenceInput {
|
|
3
|
+
predicate: ConvergencePredicate;
|
|
4
|
+
gates: GateResult[];
|
|
5
|
+
findings: ReviewFinding[];
|
|
6
|
+
/** A cross-family clean review exists for the final diff. */
|
|
7
|
+
finalReviewClean: boolean;
|
|
8
|
+
/** The final diff has not changed since the final review (not stale). */
|
|
9
|
+
diffStableAfterReview: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface ConvergenceResult {
|
|
12
|
+
converged: boolean;
|
|
13
|
+
reasons: string[];
|
|
14
|
+
openBlockers: ReviewFinding[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Evaluate the convergence predicate. Convergence does NOT mean "no model can
|
|
18
|
+
* invent any new nit" — it means no accepted important finding remains open,
|
|
19
|
+
* required gates pass, and the final scope-valid review is clean/stable.
|
|
20
|
+
*/
|
|
21
|
+
export declare function evaluateConvergence(input: ConvergenceInput): ConvergenceResult;
|
|
22
|
+
//# sourceMappingURL=convergence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convergence.d.ts","sourceRoot":"","sources":["../src/convergence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIzF,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,oBAAoB,CAAC;IAChC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,6DAA6D;IAC7D,gBAAgB,EAAE,OAAO,CAAC;IAC1B,yEAAyE;IACzE,qBAAqB,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,aAAa,EAAE,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CA4B9E"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { isBlocking } from "@claudexor/schema";
|
|
2
|
+
import { gatesPassed } from "./gates.js";
|
|
3
|
+
/**
|
|
4
|
+
* Evaluate the convergence predicate. Convergence does NOT mean "no model can
|
|
5
|
+
* invent any new nit" — it means no accepted important finding remains open,
|
|
6
|
+
* required gates pass, and the final scope-valid review is clean/stable.
|
|
7
|
+
*/
|
|
8
|
+
export function evaluateConvergence(input) {
|
|
9
|
+
const reasons = [];
|
|
10
|
+
const p = input.predicate;
|
|
11
|
+
const openBlockers = input.findings.filter((f) => isBlocking(f));
|
|
12
|
+
if (p.require_tests_pass && !gatesPassed(input.gates)) {
|
|
13
|
+
reasons.push("required gates are not all passing");
|
|
14
|
+
}
|
|
15
|
+
if (p.require_no_accepted_block_open && openBlockers.some((f) => f.severity === "BLOCK")) {
|
|
16
|
+
reasons.push("an accepted BLOCK finding is open");
|
|
17
|
+
}
|
|
18
|
+
if (p.require_no_accepted_fix_first_open && openBlockers.some((f) => f.severity === "FIX_FIRST")) {
|
|
19
|
+
reasons.push("an accepted FIX_FIRST finding is open");
|
|
20
|
+
}
|
|
21
|
+
// An accepted NEEDS_HUMAN escalation blocks convergence on its own. Without
|
|
22
|
+
// this, a run with cross-family clean review disabled could converge to
|
|
23
|
+
// success with an open NEEDS_HUMAN (the v0.8 hole), bypassing the human gate.
|
|
24
|
+
if (p.require_no_accepted_needs_human_open && openBlockers.some((f) => f.severity === "NEEDS_HUMAN")) {
|
|
25
|
+
reasons.push("an accepted NEEDS_HUMAN escalation is open");
|
|
26
|
+
}
|
|
27
|
+
if (p.require_final_cross_family_clean_review && !input.finalReviewClean) {
|
|
28
|
+
reasons.push("no final cross-family clean review");
|
|
29
|
+
}
|
|
30
|
+
if (p.require_final_diff_stable_after_review && !input.diffStableAfterReview) {
|
|
31
|
+
reasons.push("diff changed after final review (review is stale)");
|
|
32
|
+
}
|
|
33
|
+
return { converged: reasons.length === 0, reasons, openBlockers };
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=convergence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convergence.js","sourceRoot":"","sources":["../src/convergence.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAkBzC;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAuB;IACzD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAEjE,IAAI,CAAC,CAAC,kBAAkB,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACtD,OAAO,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,CAAC,CAAC,8BAA8B,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,EAAE,CAAC;QACzF,OAAO,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,CAAC,CAAC,kCAAkC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,WAAW,CAAC,EAAE,CAAC;QACjG,OAAO,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IACxD,CAAC;IACD,4EAA4E;IAC5E,wEAAwE;IACxE,8EAA8E;IAC9E,IAAI,CAAC,CAAC,oCAAoC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,aAAa,CAAC,EAAE,CAAC;QACrG,OAAO,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,CAAC,CAAC,uCAAuC,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC;QACzE,OAAO,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,CAAC,CAAC,sCAAsC,IAAI,CAAC,KAAK,CAAC,qBAAqB,EAAE,CAAC;QAC7E,OAAO,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;AACpE,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReviewFinding, RouteProofStatus } from "@claudexor/schema";
|
|
2
|
+
/** Extract JSON payloads from a reviewer's free-text output (fenced or bare). */
|
|
3
|
+
export declare function extractJsonBlocks(text: string): unknown[];
|
|
4
|
+
export interface ReviewerInfo {
|
|
5
|
+
harness_id: string;
|
|
6
|
+
requested_model?: string | null;
|
|
7
|
+
requested_effort?: string | null;
|
|
8
|
+
observed_model?: string | null;
|
|
9
|
+
route_proof_status?: RouteProofStatus;
|
|
10
|
+
}
|
|
11
|
+
export declare function parseFindingsDetailed(text: string, reviewer: ReviewerInfo): {
|
|
12
|
+
findings: ReviewFinding[];
|
|
13
|
+
malformed: number;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Merge near-duplicate findings (same category + claim + file set), keeping the
|
|
17
|
+
* most severe. NEEDS_HUMAN is an orthogonal human-gate, not a severity rung — it
|
|
18
|
+
* is never collapsed into (or replaced by) another finding, so a same-key BLOCK
|
|
19
|
+
* from a second reviewer can never silently swallow a human-approval escalation.
|
|
20
|
+
*/
|
|
21
|
+
export declare function dedupeFindings(findings: ReviewFinding[]): ReviewFinding[];
|
|
22
|
+
//# sourceMappingURL=findings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings.d.ts","sourceRoot":"","sources":["../src/findings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAY,MAAM,mBAAmB,CAAC;AAKnF,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CA2HzD;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kBAAkB,CAAC,EAAE,gBAAgB,CAAC;CACvC;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,YAAY,GACrB;IAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAkDlD;AAgBD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAwBzE"}
|
package/dist/findings.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { ReviewFinding as ReviewFindingSchema } from "@claudexor/schema";
|
|
2
|
+
import { newId } from "@claudexor/util";
|
|
3
|
+
/** Extract JSON payloads from a reviewer's free-text output (fenced or bare). */
|
|
4
|
+
export function extractJsonBlocks(text) {
|
|
5
|
+
const results = [];
|
|
6
|
+
const isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
7
|
+
const isSingleFindingObject = (value) => "severity" in value && ("claim" in value || "message" in value || "evidence" in value);
|
|
8
|
+
const isReviewPayload = (value, allowSingleObject) => {
|
|
9
|
+
if (Array.isArray(value))
|
|
10
|
+
return true;
|
|
11
|
+
if (!isRecord(value))
|
|
12
|
+
return false;
|
|
13
|
+
if (Array.isArray(value.findings))
|
|
14
|
+
return true;
|
|
15
|
+
return allowSingleObject && isSingleFindingObject(value);
|
|
16
|
+
};
|
|
17
|
+
const tryParse = (candidate, allowSingleObject = false) => {
|
|
18
|
+
const trimmed = candidate.trim();
|
|
19
|
+
if (!trimmed)
|
|
20
|
+
return false;
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(trimmed);
|
|
23
|
+
if (!isReviewPayload(parsed, allowSingleObject))
|
|
24
|
+
return false;
|
|
25
|
+
results.push(parsed);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const findBalancedJsonEnd = (source, start) => {
|
|
33
|
+
const open = source[start];
|
|
34
|
+
if (open !== "[" && open !== "{")
|
|
35
|
+
return null;
|
|
36
|
+
const stack = [open];
|
|
37
|
+
let inString = false;
|
|
38
|
+
let escaped = false;
|
|
39
|
+
for (let i = start + 1; i < source.length; i += 1) {
|
|
40
|
+
const ch = source[i] ?? "";
|
|
41
|
+
if (inString) {
|
|
42
|
+
if (escaped) {
|
|
43
|
+
escaped = false;
|
|
44
|
+
}
|
|
45
|
+
else if (ch === "\\") {
|
|
46
|
+
escaped = true;
|
|
47
|
+
}
|
|
48
|
+
else if (ch === '"') {
|
|
49
|
+
inString = false;
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (ch === '"') {
|
|
54
|
+
inString = true;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (ch === "[" || ch === "{") {
|
|
58
|
+
stack.push(ch);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (ch === "]" || ch === "}") {
|
|
62
|
+
const expected = ch === "]" ? "[" : "{";
|
|
63
|
+
if (stack.pop() !== expected)
|
|
64
|
+
return null;
|
|
65
|
+
if (stack.length === 0)
|
|
66
|
+
return i + 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
};
|
|
71
|
+
const isWhitespaceOnly = (source, start) => {
|
|
72
|
+
for (let i = start; i < source.length; i += 1) {
|
|
73
|
+
const ch = source[i] ?? "";
|
|
74
|
+
if (ch !== " " && ch !== "\t" && ch !== "\n" && ch !== "\r")
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
};
|
|
79
|
+
const jsonLineStarts = (source, open) => {
|
|
80
|
+
const starts = [];
|
|
81
|
+
let lineStart = 0;
|
|
82
|
+
for (let i = 0; i <= source.length; i += 1) {
|
|
83
|
+
if (i < source.length && source[i] !== "\n")
|
|
84
|
+
continue;
|
|
85
|
+
const lineEnd = i > lineStart && source[i - 1] === "\r" ? i - 1 : i;
|
|
86
|
+
let first = lineStart;
|
|
87
|
+
while (first < lineEnd) {
|
|
88
|
+
const ch = source[first] ?? "";
|
|
89
|
+
if (ch !== " " && ch !== "\t")
|
|
90
|
+
break;
|
|
91
|
+
first += 1;
|
|
92
|
+
}
|
|
93
|
+
const ch = source[first] ?? "";
|
|
94
|
+
if (first < lineEnd && ch === open)
|
|
95
|
+
starts.push(first);
|
|
96
|
+
lineStart = i + 1;
|
|
97
|
+
}
|
|
98
|
+
return starts;
|
|
99
|
+
};
|
|
100
|
+
const fence = /```(?:json)?\s*([\s\S]*?)```/g;
|
|
101
|
+
let m;
|
|
102
|
+
let found = false;
|
|
103
|
+
while ((m = fence.exec(text)) !== null) {
|
|
104
|
+
found = tryParse(m[1] ?? "", true) || found;
|
|
105
|
+
}
|
|
106
|
+
if (!found) {
|
|
107
|
+
const trimmed = text.trim();
|
|
108
|
+
if (!tryParse(trimmed, true)) {
|
|
109
|
+
const arrayStarts = jsonLineStarts(trimmed, "[");
|
|
110
|
+
const starts = arrayStarts.length > 0 ? arrayStarts : jsonLineStarts(trimmed, "{");
|
|
111
|
+
for (let i = starts.length - 1; i >= 0; i -= 1) {
|
|
112
|
+
const start = starts[i] ?? 0;
|
|
113
|
+
const end = findBalancedJsonEnd(trimmed, start);
|
|
114
|
+
if (end === null)
|
|
115
|
+
continue;
|
|
116
|
+
const candidate = trimmed.slice(start, end);
|
|
117
|
+
if (isWhitespaceOnly(trimmed, end)) {
|
|
118
|
+
if (tryParse(candidate, true))
|
|
119
|
+
break;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
// Some native transcripts duplicate status text after the model's final
|
|
123
|
+
// JSON block. Prefer the last complete line-start JSON block over
|
|
124
|
+
// discarding an otherwise valid reviewer response.
|
|
125
|
+
if (tryParse(candidate, true))
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const lines = trimmed.split(/\r?\n/);
|
|
130
|
+
if (results.length === 0) {
|
|
131
|
+
for (const line of lines) {
|
|
132
|
+
const candidate = line.trim();
|
|
133
|
+
if ((candidate.startsWith("[") && candidate.endsWith("]")) ||
|
|
134
|
+
(candidate.startsWith("{") && candidate.endsWith("}"))) {
|
|
135
|
+
if (tryParse(candidate, true))
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
export function parseFindingsDetailed(text, reviewer) {
|
|
144
|
+
const raw = [];
|
|
145
|
+
for (const block of extractJsonBlocks(text)) {
|
|
146
|
+
if (Array.isArray(block))
|
|
147
|
+
raw.push(...block);
|
|
148
|
+
else if (block && typeof block === "object") {
|
|
149
|
+
const candidate = block;
|
|
150
|
+
if (Array.isArray(candidate.findings))
|
|
151
|
+
raw.push(...candidate.findings);
|
|
152
|
+
else
|
|
153
|
+
raw.push(block);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const out = [];
|
|
157
|
+
let malformed = 0;
|
|
158
|
+
for (const r of raw) {
|
|
159
|
+
if (!r || typeof r !== "object") {
|
|
160
|
+
malformed += 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
// A finding WITHOUT a severity is malformed, not "WARN by default": the
|
|
164
|
+
// fail-closed verdict parse must never silently downgrade what might have
|
|
165
|
+
// been a blocker into a non-blocking level (the one lenient branch this
|
|
166
|
+
// parser used to have).
|
|
167
|
+
if (r.severity === undefined || r.severity === null) {
|
|
168
|
+
malformed += 1;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
out.push(ReviewFindingSchema.parse({
|
|
173
|
+
id: r.id ?? newId("f"),
|
|
174
|
+
severity: r.severity,
|
|
175
|
+
category: r.category ?? "correctness",
|
|
176
|
+
claim: String(r.claim ?? r.message ?? "(no claim)"),
|
|
177
|
+
linked_acceptance_criteria: r.linked_acceptance_criteria ?? [],
|
|
178
|
+
evidence: r.evidence ?? {},
|
|
179
|
+
proposed_fix: r.proposed_fix ?? null,
|
|
180
|
+
reviewer: {
|
|
181
|
+
harness_id: reviewer.harness_id,
|
|
182
|
+
requested_model: reviewer.requested_model ?? null,
|
|
183
|
+
requested_effort: reviewer.requested_effort ?? null,
|
|
184
|
+
observed_model: reviewer.observed_model ?? null,
|
|
185
|
+
route_proof_status: reviewer.route_proof_status ?? "unverified",
|
|
186
|
+
},
|
|
187
|
+
status: "proposed",
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
malformed += 1;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { findings: out, malformed };
|
|
195
|
+
}
|
|
196
|
+
const SEVERITY_ORDER = [
|
|
197
|
+
"INSUFFICIENT_EVIDENCE",
|
|
198
|
+
"NIT",
|
|
199
|
+
"OUT_OF_SCOPE",
|
|
200
|
+
"WARN",
|
|
201
|
+
"NEEDS_HUMAN",
|
|
202
|
+
"FIX_FIRST",
|
|
203
|
+
"BLOCK",
|
|
204
|
+
];
|
|
205
|
+
function severityRank(s) {
|
|
206
|
+
return SEVERITY_ORDER.indexOf(s);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Merge near-duplicate findings (same category + claim + file set), keeping the
|
|
210
|
+
* most severe. NEEDS_HUMAN is an orthogonal human-gate, not a severity rung — it
|
|
211
|
+
* is never collapsed into (or replaced by) another finding, so a same-key BLOCK
|
|
212
|
+
* from a second reviewer can never silently swallow a human-approval escalation.
|
|
213
|
+
*/
|
|
214
|
+
export function dedupeFindings(findings) {
|
|
215
|
+
const seen = new Map();
|
|
216
|
+
const humanGates = [];
|
|
217
|
+
const insufficientEvidence = [];
|
|
218
|
+
for (const f of findings) {
|
|
219
|
+
if (f.severity === "NEEDS_HUMAN") {
|
|
220
|
+
humanGates.push(f);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (f.severity === "INSUFFICIENT_EVIDENCE") {
|
|
224
|
+
insufficientEvidence.push(f);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const files = f.evidence.files
|
|
228
|
+
.map((x) => x.path)
|
|
229
|
+
.sort()
|
|
230
|
+
.join(",");
|
|
231
|
+
const key = `${f.category}|${f.claim.toLowerCase().slice(0, 120)}|${files}`;
|
|
232
|
+
const existing = seen.get(key);
|
|
233
|
+
if (!existing || severityRank(f.severity) > severityRank(existing.severity)) {
|
|
234
|
+
seen.set(key, f);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return [...seen.values(), ...humanGates, ...insufficientEvidence];
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=findings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings.js","sourceRoot":"","sources":["../src/findings.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,IAAI,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAGxC,iFAAiF;AACjF,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,OAAO,GAAc,EAAE,CAAC;IAC9B,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAoC,EAAE,CACpE,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAChE,MAAM,qBAAqB,GAAG,CAAC,KAA8B,EAAW,EAAE,CACxE,UAAU,IAAI,KAAK,IAAI,CAAC,OAAO,IAAI,KAAK,IAAI,SAAS,IAAI,KAAK,IAAI,UAAU,IAAI,KAAK,CAAC,CAAC;IACzF,MAAM,eAAe,GAAG,CAAC,KAAc,EAAE,iBAA0B,EAAW,EAAE;QAC9E,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACtC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACnC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC/C,OAAO,iBAAiB,IAAI,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,CAAC,SAAiB,EAAE,iBAAiB,GAAG,KAAK,EAAW,EAAE;QACzE,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACnC,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,iBAAiB,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,CAAC;IACF,MAAM,mBAAmB,GAAG,CAAC,MAAc,EAAE,KAAa,EAAiB,EAAE;QAC3E,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAC9C,MAAM,KAAK,GAAa,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,KAAK,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAClD,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3B,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO,GAAG,KAAK,CAAC;gBAClB,CAAC;qBAAM,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;oBACvB,OAAO,GAAG,IAAI,CAAC;gBACjB,CAAC;qBAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;oBACtB,QAAQ,GAAG,KAAK,CAAC;gBACnB,CAAC;gBACD,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,QAAQ,GAAG,IAAI,CAAC;gBAChB,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACf,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBAC7B,MAAM,QAAQ,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;gBACxC,IAAI,KAAK,CAAC,GAAG,EAAE,KAAK,QAAQ;oBAAE,OAAO,IAAI,CAAC;gBAC1C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;oBAAE,OAAO,CAAC,GAAG,CAAC,CAAC;YACvC,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IACF,MAAM,gBAAgB,GAAG,CAAC,MAAc,EAAE,KAAa,EAAW,EAAE;QAClE,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9C,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3B,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;QAC5E,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IACF,MAAM,cAAc,GAAG,CAAC,MAAc,EAAE,IAAe,EAAY,EAAE;QACnE,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3C,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI;gBAAE,SAAS;YACtD,MAAM,OAAO,GAAG,CAAC,GAAG,SAAS,IAAI,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACpE,IAAI,KAAK,GAAG,SAAS,CAAC;YACtB,OAAO,KAAK,GAAG,OAAO,EAAE,CAAC;gBACvB,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;gBAC/B,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI;oBAAE,MAAM;gBACrC,KAAK,IAAI,CAAC,CAAC;YACb,CAAC;YACD,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YAC/B,IAAI,KAAK,GAAG,OAAO,IAAI,EAAE,KAAK,IAAI;gBAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvD,SAAS,GAAG,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,+BAA+B,CAAC;IAC9C,IAAI,CAAyB,CAAC;IAC9B,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACvC,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC;IAC9C,CAAC;IACD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;YAC7B,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACjD,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACnF,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;gBAC7B,MAAM,GAAG,GAAG,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;gBAChD,IAAI,GAAG,KAAK,IAAI;oBAAE,SAAS;gBAC3B,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;gBAC5C,IAAI,gBAAgB,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;oBACnC,IAAI,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;wBAAE,MAAM;oBACrC,SAAS;gBACX,CAAC;gBACD,wEAAwE;gBACxE,kEAAkE;gBAClE,mDAAmD;gBACnD,IAAI,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;oBAAE,MAAM;YACvC,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC9B,IACE,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;oBACtD,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EACtD,CAAC;oBACD,IAAI,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;wBAAE,MAAM;gBACvC,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAUD,MAAM,UAAU,qBAAqB,CACnC,IAAY,EACZ,QAAsB;IAEtB,MAAM,GAAG,GAAU,EAAE,CAAC;IACtB,KAAK,MAAM,KAAK,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;aACxC,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC5C,MAAM,SAAS,GAAG,KAA+B,CAAC;YAClD,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;;gBAClE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;QACpB,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;YAChC,SAAS,IAAI,CAAC,CAAC;YACf,SAAS;QACX,CAAC;QACD,wEAAwE;QACxE,0EAA0E;QAC1E,wEAAwE;QACxE,wBAAwB;QACxB,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YACpD,SAAS,IAAI,CAAC,CAAC;YACf,SAAS;QACX,CAAC;QACD,IAAI,CAAC;YACH,GAAG,CAAC,IAAI,CACN,mBAAmB,CAAC,KAAK,CAAC;gBACxB,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,CAAC,GAAG,CAAC;gBACtB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,aAAa;gBACrC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,OAAO,IAAI,YAAY,CAAC;gBACnD,0BAA0B,EAAE,CAAC,CAAC,0BAA0B,IAAI,EAAE;gBAC9D,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE;gBAC1B,YAAY,EAAE,CAAC,CAAC,YAAY,IAAI,IAAI;gBACpC,QAAQ,EAAE;oBACR,UAAU,EAAE,QAAQ,CAAC,UAAU;oBAC/B,eAAe,EAAE,QAAQ,CAAC,eAAe,IAAI,IAAI;oBACjD,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,IAAI,IAAI;oBACnD,cAAc,EAAE,QAAQ,CAAC,cAAc,IAAI,IAAI;oBAC/C,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB,IAAI,YAAY;iBAChE;gBACD,MAAM,EAAE,UAAU;aACnB,CAAC,CACH,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,SAAS,IAAI,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,cAAc,GAAe;IACjC,uBAAuB;IACvB,KAAK;IACL,cAAc;IACd,MAAM;IACN,aAAa;IACb,WAAW;IACX,OAAO;CACR,CAAC;AAEF,SAAS,YAAY,CAAC,CAAW;IAC/B,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,QAAyB;IACtD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC9C,MAAM,UAAU,GAAoB,EAAE,CAAC;IACvC,MAAM,oBAAoB,GAAoB,EAAE,CAAC;IACjD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,QAAQ,KAAK,aAAa,EAAE,CAAC;YACjC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,CAAC,QAAQ,KAAK,uBAAuB,EAAE,CAAC;YAC3C,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC7B,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK;aAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAClB,IAAI,EAAE;aACN,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC;QAC5E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC,QAAQ,IAAI,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5E,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,GAAG,UAAU,EAAE,GAAG,oBAAoB,CAAC,CAAC;AACpE,CAAC"}
|
package/dist/gates.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { GateResult } from "@claudexor/schema";
|
|
2
|
+
export interface GateSpec {
|
|
3
|
+
id: string;
|
|
4
|
+
command: string;
|
|
5
|
+
required?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface RunGatesOptions {
|
|
8
|
+
cwd: string;
|
|
9
|
+
env?: Record<string, string>;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
/** Cancellation: checked between gates AND passed into each gate's process
|
|
12
|
+
* (a cancel must not wait out a 600s gate before acknowledging). */
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
}
|
|
15
|
+
/** Run one deterministic gate. Outcome is decided by exit code, never by parsing text. */
|
|
16
|
+
export declare function runGate(spec: GateSpec, opts: RunGatesOptions): Promise<GateResult>;
|
|
17
|
+
export declare function runGates(specs: GateSpec[], opts: RunGatesOptions): Promise<GateResult[]>;
|
|
18
|
+
export declare function gatesPassed(gates: GateResult[]): boolean;
|
|
19
|
+
//# sourceMappingURL=gates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../src/gates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAOpD,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;wEACoE;IACpE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,0FAA0F;AAC1F,wBAAsB,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAuCxF;AAED,wBAAsB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAS9F;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAExD"}
|
package/dist/gates.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { GateResult as GateResultSchema } from "@claudexor/schema";
|
|
2
|
+
import { runCapture } from "@claudexor/core";
|
|
3
|
+
import { redactSecrets } from "@claudexor/util";
|
|
4
|
+
const GATE_OUTPUT_TAIL_CHARS = 12_000;
|
|
5
|
+
/** Run one deterministic gate. Outcome is decided by exit code, never by parsing text. */
|
|
6
|
+
export async function runGate(spec, opts) {
|
|
7
|
+
const start = Date.now();
|
|
8
|
+
let code = null;
|
|
9
|
+
let timedOut = false;
|
|
10
|
+
let stdout = "";
|
|
11
|
+
let stderr = "";
|
|
12
|
+
try {
|
|
13
|
+
const r = await runCapture("sh", ["-c", spec.command], {
|
|
14
|
+
cwd: opts.cwd,
|
|
15
|
+
env: opts.env,
|
|
16
|
+
timeoutMs: opts.timeoutMs ?? 600_000,
|
|
17
|
+
abortSignal: opts.signal,
|
|
18
|
+
});
|
|
19
|
+
code = r.code;
|
|
20
|
+
stdout = r.stdout;
|
|
21
|
+
stderr = r.stderr;
|
|
22
|
+
if (r.signal === "SIGKILL")
|
|
23
|
+
timedOut = true;
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
// A gate whose SPAWN itself throws is still an honest `failed`, but the
|
|
27
|
+
// reason must survive as evidence — exit_code:null with empty tails is
|
|
28
|
+
// undiagnosable ("evidence beats summaries").
|
|
29
|
+
code = null;
|
|
30
|
+
stderr = `gate spawn failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
31
|
+
}
|
|
32
|
+
const status = timedOut ? "timed_out" : code === 0 ? "passed" : "failed";
|
|
33
|
+
const includeOutput = status !== "passed";
|
|
34
|
+
const stdoutEvidence = includeOutput ? outputTail(stdout) : EMPTY_OUTPUT_TAIL;
|
|
35
|
+
const stderrEvidence = includeOutput ? outputTail(stderr) : EMPTY_OUTPUT_TAIL;
|
|
36
|
+
return GateResultSchema.parse({
|
|
37
|
+
id: spec.id,
|
|
38
|
+
command: spec.command,
|
|
39
|
+
exit_code: code,
|
|
40
|
+
status,
|
|
41
|
+
duration_ms: Date.now() - start,
|
|
42
|
+
required: spec.required ?? true,
|
|
43
|
+
stdout_tail: stdoutEvidence.tail,
|
|
44
|
+
stderr_tail: stderrEvidence.tail,
|
|
45
|
+
output_truncated: stdoutEvidence.truncated || stderrEvidence.truncated,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export async function runGates(specs, opts) {
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const spec of specs) {
|
|
51
|
+
// Abort between gates: remaining gates are simply not run (the attempt is
|
|
52
|
+
// being cancelled; burning their full timeouts would delay the ack).
|
|
53
|
+
if (opts.signal?.aborted)
|
|
54
|
+
break;
|
|
55
|
+
out.push(await runGate(spec, opts));
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
export function gatesPassed(gates) {
|
|
60
|
+
return gates.filter((g) => g.required).every((g) => g.status === "passed");
|
|
61
|
+
}
|
|
62
|
+
const EMPTY_OUTPUT_TAIL = { tail: null, truncated: false };
|
|
63
|
+
function outputTail(text) {
|
|
64
|
+
const redacted = redactSecrets(text).trimEnd();
|
|
65
|
+
if (!redacted)
|
|
66
|
+
return EMPTY_OUTPUT_TAIL;
|
|
67
|
+
return {
|
|
68
|
+
tail: redacted.slice(-GATE_OUTPUT_TAIL_CHARS),
|
|
69
|
+
truncated: redacted.length > GATE_OUTPUT_TAIL_CHARS,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=gates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gates.js","sourceRoot":"","sources":["../src/gates.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./gates.js";
|
|
2
|
+
export * from "./route.js";
|
|
3
|
+
export * from "./findings.js";
|
|
4
|
+
export * from "./revalidate.js";
|
|
5
|
+
export * from "./convergence.js";
|
|
6
|
+
export * from "./readiness.js";
|
|
7
|
+
export * from "./reviewEngine.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./gates.js";
|
|
2
|
+
export * from "./route.js";
|
|
3
|
+
export * from "./findings.js";
|
|
4
|
+
export * from "./revalidate.js";
|
|
5
|
+
export * from "./convergence.js";
|
|
6
|
+
export * from "./readiness.js";
|
|
7
|
+
export * from "./reviewEngine.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ReadinessDebt {
|
|
2
|
+
signature: string;
|
|
3
|
+
count: number;
|
|
4
|
+
lastReason: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Tracks "commit-readiness debts" to detect thrashing. When a
|
|
8
|
+
* failure signature repeats, the loop should change strategy (in
|
|
9
|
+
* until_clean) or stop and ask (in adversarial-review), rather than
|
|
10
|
+
* re-running the same failing approach.
|
|
11
|
+
*/
|
|
12
|
+
export declare class ReadinessLedger {
|
|
13
|
+
private readonly debts;
|
|
14
|
+
recordRound(signature: string, reason: string): void;
|
|
15
|
+
/** True when the same failure signature has recurred >= threshold times. */
|
|
16
|
+
isStalled(signature: string, threshold?: number): boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Stable signature for a set of failure reasons, for thrash detection. */
|
|
19
|
+
export declare function failureSignature(reasons: string[]): string;
|
|
20
|
+
//# sourceMappingURL=readiness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAE1D,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAUpD,4EAA4E;IAC5E,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,SAAI,GAAG,OAAO;CAGrD;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAE1D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks "commit-readiness debts" to detect thrashing. When a
|
|
3
|
+
* failure signature repeats, the loop should change strategy (in
|
|
4
|
+
* until_clean) or stop and ask (in adversarial-review), rather than
|
|
5
|
+
* re-running the same failing approach.
|
|
6
|
+
*/
|
|
7
|
+
export class ReadinessLedger {
|
|
8
|
+
debts = new Map();
|
|
9
|
+
recordRound(signature, reason) {
|
|
10
|
+
const debt = this.debts.get(signature);
|
|
11
|
+
if (debt) {
|
|
12
|
+
debt.count += 1;
|
|
13
|
+
debt.lastReason = reason;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
this.debts.set(signature, { signature, count: 1, lastReason: reason });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** True when the same failure signature has recurred >= threshold times. */
|
|
20
|
+
isStalled(signature, threshold = 2) {
|
|
21
|
+
return (this.debts.get(signature)?.count ?? 0) >= threshold;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Stable signature for a set of failure reasons, for thrash detection. */
|
|
25
|
+
export function failureSignature(reasons) {
|
|
26
|
+
return reasons.slice().map((r) => r.toLowerCase()).sort().join(" | ");
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=readiness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readiness.js","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"AAMA;;;;;GAKG;AACH,MAAM,OAAO,eAAe;IACT,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE1D,WAAW,CAAC,SAAiB,EAAE,MAAc;QAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;YAChB,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,SAAS,CAAC,SAAiB,EAAE,SAAS,GAAG,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,IAAI,SAAS,CAAC;IAC9D,CAAC;CACF;AAED,2EAA2E;AAC3E,MAAM,UAAU,gBAAgB,CAAC,OAAiB;IAChD,OAAO,OAAO,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACxE,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { FindingStatus, ReviewFinding } from "@claudexor/schema";
|
|
2
|
+
export interface RevalidateDecision {
|
|
3
|
+
status: FindingStatus;
|
|
4
|
+
note?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface RevalidateOptions {
|
|
7
|
+
candidateRoot?: string;
|
|
8
|
+
evidenceDir?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Deterministic revalidation (no LLM): enforces the evidence invariants that
|
|
12
|
+
* always hold — no evidence -> cannot block; out-of-scope/insufficient pass
|
|
13
|
+
* through; a NEEDS_HUMAN escalation stays a blocking human gate.
|
|
14
|
+
*/
|
|
15
|
+
export declare function deterministicDecision(f: ReviewFinding, options?: RevalidateOptions): RevalidateDecision;
|
|
16
|
+
/**
|
|
17
|
+
* Revalidate every finding deterministically. Each finding's status is recorded;
|
|
18
|
+
* the evidence-free BLOCK/FIX_FIRST invariant is applied so a reviewer cannot
|
|
19
|
+
* leave an evidence-free finding in a blocking state.
|
|
20
|
+
*/
|
|
21
|
+
export declare function revalidateFindings(findings: ReviewFinding[], options?: RevalidateOptions): Promise<ReviewFinding[]>;
|
|
22
|
+
//# sourceMappingURL=revalidate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revalidate.d.ts","sourceRoot":"","sources":["../src/revalidate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA8DD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,CAAC,EAAE,aAAa,EAChB,OAAO,GAAE,iBAAsB,GAC9B,kBAAkB,CAuBpB;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,aAAa,EAAE,EACzB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,aAAa,EAAE,CAAC,CAQ1B"}
|