argus-reviewer-e2e 0.2.0 → 0.3.1
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 +77 -64
- package/action/action.yml +149 -66
- package/action/approval-review.mjs +393 -0
- package/action/bootstrap.mjs +104 -0
- package/action/cli.mjs +36 -0
- package/action/emit-review.mjs +336 -0
- package/action/runtime.mjs +166 -0
- package/action/{sticky-comment.mjs → sticky-comment.cjs} +360 -74
- package/dist/api.d.ts +1 -0
- package/dist/api.js +3 -0
- package/dist/cache/store.d.ts +2 -0
- package/dist/cache/store.js +10 -3
- package/dist/cli.d.ts +80 -10
- package/dist/cli.js +302 -13
- package/dist/config.d.ts +4 -0
- package/dist/config.js +32 -4
- package/dist/engine/loop.d.ts +12 -0
- package/dist/engine/loop.js +55 -6
- package/dist/pipeline/budget.d.ts +11 -0
- package/dist/pipeline/budget.js +48 -0
- package/dist/pipeline/contracts.d.ts +12 -0
- package/dist/pipeline/contracts.js +16 -0
- package/dist/pipeline/verify.d.ts +27 -0
- package/dist/pipeline/verify.js +197 -0
- package/dist/report/manifest.d.ts +87 -0
- package/dist/report/manifest.js +143 -0
- package/dist/report/run.d.ts +9 -0
- package/dist/report/run.js +6 -0
- package/dist/review/secrets.d.ts +2 -0
- package/dist/review/secrets.js +4 -0
- package/package.json +7 -7
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type ExecFn } from '../detect.js';
|
|
2
|
+
import type { CallCost } from '../vision/cost.js';
|
|
3
|
+
/** Whether a report's source can be tied to the intended PR head. */
|
|
4
|
+
export type HeadBindingStatus = 'match' | 'mismatch' | 'unknown' | 'not_applicable';
|
|
5
|
+
export type HeadSource = 'github' | 'fixture' | 'local';
|
|
6
|
+
export declare const LANE_IDS: readonly ["review", "flow", "app", "a0"];
|
|
7
|
+
export type LaneId = (typeof LANE_IDS)[number];
|
|
8
|
+
export declare const LANE_STATUSES: readonly ["passed", "failed", "skipped", "blocked", "unavailable", "inconclusive"];
|
|
9
|
+
export type LaneStatus = (typeof LANE_STATUSES)[number];
|
|
10
|
+
export declare const MANIFEST_SCHEMA_VERSION: 1;
|
|
11
|
+
export interface HeadBinding {
|
|
12
|
+
intendedSha: string | undefined;
|
|
13
|
+
checkoutSha: string | undefined;
|
|
14
|
+
status: HeadBindingStatus;
|
|
15
|
+
source: HeadSource;
|
|
16
|
+
detail: string;
|
|
17
|
+
}
|
|
18
|
+
export interface UsageSummary {
|
|
19
|
+
provider: 'openrouter' | 'a0' | 'unknown';
|
|
20
|
+
model: string | undefined;
|
|
21
|
+
calls: number;
|
|
22
|
+
tokens: number;
|
|
23
|
+
costUsd: number;
|
|
24
|
+
metered: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface BudgetSummary {
|
|
27
|
+
limitUsd: number | undefined;
|
|
28
|
+
spentUsd: number;
|
|
29
|
+
exceeded: boolean;
|
|
30
|
+
maxDurationMs: number | undefined;
|
|
31
|
+
elapsedMs: number;
|
|
32
|
+
maxTasks: number | undefined;
|
|
33
|
+
tasks: number;
|
|
34
|
+
}
|
|
35
|
+
export interface LaneManifest {
|
|
36
|
+
lane: LaneId;
|
|
37
|
+
selected: boolean;
|
|
38
|
+
status: LaneStatus;
|
|
39
|
+
startedAt: string | undefined;
|
|
40
|
+
finishedAt: string | undefined;
|
|
41
|
+
reportPath: string | undefined;
|
|
42
|
+
model: string | undefined;
|
|
43
|
+
summary: string | undefined;
|
|
44
|
+
reason: string | undefined;
|
|
45
|
+
usage: UsageSummary;
|
|
46
|
+
budget: BudgetSummary;
|
|
47
|
+
headBinding: HeadBinding | undefined;
|
|
48
|
+
}
|
|
49
|
+
export interface RunIdentity {
|
|
50
|
+
repo: string | undefined;
|
|
51
|
+
pr: string | undefined;
|
|
52
|
+
intendedHeadSha: string | undefined;
|
|
53
|
+
checkoutSha: string | undefined;
|
|
54
|
+
baseSha: string | undefined;
|
|
55
|
+
}
|
|
56
|
+
export interface RunManifest {
|
|
57
|
+
schemaVersion: typeof MANIFEST_SCHEMA_VERSION;
|
|
58
|
+
runId: string;
|
|
59
|
+
startedAt: string;
|
|
60
|
+
finishedAt: string;
|
|
61
|
+
identity: RunIdentity;
|
|
62
|
+
lanes: Record<LaneId, LaneManifest>;
|
|
63
|
+
aggregate: {
|
|
64
|
+
status: LaneStatus;
|
|
65
|
+
ok: boolean;
|
|
66
|
+
costUsd: number;
|
|
67
|
+
calls: number;
|
|
68
|
+
tokens: number;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Read the checked-out commit without making git identity a hard dependency. */
|
|
72
|
+
export declare function readCheckoutSha(cwd: string, exec?: ExecFn): Promise<string | undefined>;
|
|
73
|
+
/** Classify the relationship between API/fixture head identity and the checkout. */
|
|
74
|
+
export declare function classifyHeadBinding(intendedSha: string | undefined, checkoutSha: string | undefined, source: HeadSource): HeadBinding;
|
|
75
|
+
/** True when runtime evidence is bound to the intended head (or a fixture). */
|
|
76
|
+
export declare function isHeadBindingConclusive(binding: HeadBinding | undefined): boolean;
|
|
77
|
+
export declare function emptyUsage(provider?: UsageSummary['provider']): UsageSummary;
|
|
78
|
+
export declare function emptyBudget(): BudgetSummary;
|
|
79
|
+
export declare function emptyLane(lane: LaneId, selected: boolean): LaneManifest;
|
|
80
|
+
export declare function aggregateLanes(lanes: Record<LaneId, LaneManifest>): {
|
|
81
|
+
status: LaneStatus;
|
|
82
|
+
ok: boolean;
|
|
83
|
+
costUsd: number;
|
|
84
|
+
calls: number;
|
|
85
|
+
tokens: number;
|
|
86
|
+
};
|
|
87
|
+
export declare function addProviderUsage(usage: UsageSummary, calls: CallCost[] | undefined): UsageSummary;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { defaultExec } from '../detect.js';
|
|
2
|
+
export const LANE_IDS = ['review', 'flow', 'app', 'a0'];
|
|
3
|
+
export const LANE_STATUSES = [
|
|
4
|
+
'passed',
|
|
5
|
+
'failed',
|
|
6
|
+
'skipped',
|
|
7
|
+
'blocked',
|
|
8
|
+
'unavailable',
|
|
9
|
+
'inconclusive',
|
|
10
|
+
];
|
|
11
|
+
export const MANIFEST_SCHEMA_VERSION = 1;
|
|
12
|
+
/** Read the checked-out commit without making git identity a hard dependency. */
|
|
13
|
+
export async function readCheckoutSha(cwd, exec = defaultExec) {
|
|
14
|
+
const result = await exec('git', ['-C', cwd, 'rev-parse', 'HEAD'], 10_000);
|
|
15
|
+
if (result.code !== 0)
|
|
16
|
+
return undefined;
|
|
17
|
+
const sha = result.stdout.trim();
|
|
18
|
+
return sha === '' ? undefined : sha;
|
|
19
|
+
}
|
|
20
|
+
/** Classify the relationship between API/fixture head identity and the checkout. */
|
|
21
|
+
export function classifyHeadBinding(intendedSha, checkoutSha, source) {
|
|
22
|
+
if (source === 'fixture') {
|
|
23
|
+
return {
|
|
24
|
+
intendedSha,
|
|
25
|
+
checkoutSha,
|
|
26
|
+
status: 'not_applicable',
|
|
27
|
+
source,
|
|
28
|
+
detail: 'fixture diff is bound to its local fixture head',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (intendedSha === undefined || intendedSha === '') {
|
|
32
|
+
return {
|
|
33
|
+
intendedSha,
|
|
34
|
+
checkoutSha,
|
|
35
|
+
status: 'unknown',
|
|
36
|
+
source,
|
|
37
|
+
detail: 'PR head identity was unavailable',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (checkoutSha === undefined || checkoutSha === '') {
|
|
41
|
+
return {
|
|
42
|
+
intendedSha,
|
|
43
|
+
checkoutSha,
|
|
44
|
+
status: 'unknown',
|
|
45
|
+
source,
|
|
46
|
+
detail: 'checkout identity was unavailable',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (intendedSha === checkoutSha) {
|
|
50
|
+
return {
|
|
51
|
+
intendedSha,
|
|
52
|
+
checkoutSha,
|
|
53
|
+
status: 'match',
|
|
54
|
+
source,
|
|
55
|
+
detail: 'checkout matches the intended PR head',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
intendedSha,
|
|
60
|
+
checkoutSha,
|
|
61
|
+
status: 'mismatch',
|
|
62
|
+
source,
|
|
63
|
+
detail: `checkout ${checkoutSha} does not match intended PR head ${intendedSha}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** True when runtime evidence is bound to the intended head (or a fixture). */
|
|
67
|
+
export function isHeadBindingConclusive(binding) {
|
|
68
|
+
return binding?.status === 'match' || binding?.status === 'not_applicable';
|
|
69
|
+
}
|
|
70
|
+
export function emptyUsage(provider = 'unknown') {
|
|
71
|
+
return {
|
|
72
|
+
provider,
|
|
73
|
+
model: undefined,
|
|
74
|
+
calls: 0,
|
|
75
|
+
tokens: 0,
|
|
76
|
+
costUsd: 0,
|
|
77
|
+
metered: provider !== 'a0',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function emptyBudget() {
|
|
81
|
+
return {
|
|
82
|
+
limitUsd: undefined,
|
|
83
|
+
spentUsd: 0,
|
|
84
|
+
exceeded: false,
|
|
85
|
+
maxDurationMs: undefined,
|
|
86
|
+
elapsedMs: 0,
|
|
87
|
+
maxTasks: undefined,
|
|
88
|
+
tasks: 0,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export function emptyLane(lane, selected) {
|
|
92
|
+
return {
|
|
93
|
+
lane,
|
|
94
|
+
selected,
|
|
95
|
+
status: selected ? 'blocked' : 'skipped',
|
|
96
|
+
startedAt: undefined,
|
|
97
|
+
finishedAt: undefined,
|
|
98
|
+
reportPath: undefined,
|
|
99
|
+
model: undefined,
|
|
100
|
+
summary: undefined,
|
|
101
|
+
reason: undefined,
|
|
102
|
+
usage: emptyUsage(),
|
|
103
|
+
budget: emptyBudget(),
|
|
104
|
+
headBinding: undefined,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export function aggregateLanes(lanes) {
|
|
108
|
+
const selected = LANE_IDS.map((lane) => lanes[lane]).filter((lane) => lane.selected);
|
|
109
|
+
const hasFailure = selected.some((lane) => ['failed', 'blocked'].includes(lane.status));
|
|
110
|
+
const hasInconclusive = selected.some((lane) => lane.status === 'inconclusive');
|
|
111
|
+
const hasUnavailable = selected.some((lane) => lane.status === 'unavailable');
|
|
112
|
+
const status = hasFailure
|
|
113
|
+
? 'failed'
|
|
114
|
+
: hasInconclusive || hasUnavailable
|
|
115
|
+
? 'inconclusive'
|
|
116
|
+
: selected.length === 0 || selected.every((lane) => lane.status === 'skipped')
|
|
117
|
+
? 'skipped'
|
|
118
|
+
: selected.every((lane) => lane.status === 'passed' || lane.status === 'skipped')
|
|
119
|
+
? 'passed'
|
|
120
|
+
: 'inconclusive';
|
|
121
|
+
return {
|
|
122
|
+
status,
|
|
123
|
+
ok: status === 'passed',
|
|
124
|
+
costUsd: selected.reduce((sum, lane) => sum + lane.usage.costUsd, 0),
|
|
125
|
+
calls: selected.reduce((sum, lane) => sum + lane.usage.calls, 0),
|
|
126
|
+
tokens: selected.reduce((sum, lane) => sum + lane.usage.tokens, 0),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export function addProviderUsage(usage, calls) {
|
|
130
|
+
if (calls === undefined || calls.length === 0)
|
|
131
|
+
return usage;
|
|
132
|
+
const costUsd = calls.reduce((sum, call) => sum + call.costUsd, 0);
|
|
133
|
+
const tokens = calls.reduce((sum, call) => sum + call.tokens, 0);
|
|
134
|
+
const model = calls.at(-1)?.model;
|
|
135
|
+
return {
|
|
136
|
+
provider: 'openrouter',
|
|
137
|
+
model: usage.model ?? model,
|
|
138
|
+
calls: usage.calls + calls.length,
|
|
139
|
+
tokens: usage.tokens + tokens,
|
|
140
|
+
costUsd: usage.costUsd + costUsd,
|
|
141
|
+
metered: true,
|
|
142
|
+
};
|
|
143
|
+
}
|
package/dist/report/run.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HealEvent, TdAssertRecord, TdStepRecord } from '../api.js';
|
|
2
|
+
import type { CacheStats } from '../engine/loop.js';
|
|
2
3
|
import type { CallCost } from '../vision/cost.js';
|
|
3
4
|
/**
|
|
4
5
|
* JSON run report consumed by the GitHub Action (R10): per-test verdicts,
|
|
@@ -20,6 +21,8 @@ export interface TestReport {
|
|
|
20
21
|
/** Per-call cost rows for model-level attribution. */
|
|
21
22
|
calls: CallCost[];
|
|
22
23
|
videoPath: string | undefined;
|
|
24
|
+
/** Fingerprint replay/grounding counters for this test. */
|
|
25
|
+
cache?: CacheStats;
|
|
23
26
|
/** Agent Zero's autonomous second opinion on a failure (heal: 'a0'). */
|
|
24
27
|
a0Diagnosis?: string;
|
|
25
28
|
}
|
|
@@ -35,6 +38,12 @@ export interface RunTotals {
|
|
|
35
38
|
costByModel: Record<string, number>;
|
|
36
39
|
/** OpenRouter call count grouped by model id. */
|
|
37
40
|
callsByModel: Record<string, number>;
|
|
41
|
+
cacheHits: number;
|
|
42
|
+
cacheMisses: number;
|
|
43
|
+
cacheHeals: number;
|
|
44
|
+
staleEntries: number;
|
|
45
|
+
assertionHits: number;
|
|
46
|
+
assertionMisses: number;
|
|
38
47
|
}
|
|
39
48
|
export interface RunReport {
|
|
40
49
|
tool: 'argus-reviewer';
|
package/dist/report/run.js
CHANGED
|
@@ -24,6 +24,12 @@ export function buildRunReport(tests, startedAt, durationMs) {
|
|
|
24
24
|
budgetExceeded: tests.some((t) => t.budgetExceeded),
|
|
25
25
|
callsByModel,
|
|
26
26
|
costByModel,
|
|
27
|
+
cacheHits: tests.reduce((sum, t) => sum + (t.cache?.hits ?? 0), 0),
|
|
28
|
+
cacheMisses: tests.reduce((sum, t) => sum + (t.cache?.misses ?? 0), 0),
|
|
29
|
+
cacheHeals: tests.reduce((sum, t) => sum + (t.cache?.heals ?? 0), 0),
|
|
30
|
+
staleEntries: tests.reduce((sum, t) => sum + (t.cache?.staleEntries ?? 0), 0),
|
|
31
|
+
assertionHits: tests.reduce((sum, t) => sum + (t.cache?.assertionHits ?? 0), 0),
|
|
32
|
+
assertionMisses: tests.reduce((sum, t) => sum + (t.cache?.assertionMisses ?? 0), 0),
|
|
27
33
|
},
|
|
28
34
|
tests,
|
|
29
35
|
artifacts: {
|
package/dist/review/secrets.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ export interface SecretsScanResult {
|
|
|
42
42
|
severity: string;
|
|
43
43
|
category?: string;
|
|
44
44
|
message: string;
|
|
45
|
+
/** Jev P(live) carried through adjudication — feeds the review gate. */
|
|
46
|
+
p?: number;
|
|
45
47
|
}[];
|
|
46
48
|
/** Audit records for report.secretsScan — literals never included. */
|
|
47
49
|
records: SecretScanRecord[];
|
package/dist/review/secrets.js
CHANGED
|
@@ -194,6 +194,10 @@ export async function scanSecrets(opts) {
|
|
|
194
194
|
severity: adjudicated ? 'bug' : 'risk',
|
|
195
195
|
category: 'security',
|
|
196
196
|
message: maskFindingMessage(c, adjudicated),
|
|
197
|
+
// A Jev-confirmed live secret counts as proven for the review-event
|
|
198
|
+
// gate — pLive IS the true-positive probability for this finding.
|
|
199
|
+
// Unadjudicated findings carry no p (degrade-open, like U8).
|
|
200
|
+
...(adjudicated && pLive !== undefined ? { p: pLive } : {}),
|
|
197
201
|
});
|
|
198
202
|
records.push({
|
|
199
203
|
file: c.file,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "argus-reviewer-e2e",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "argus-reviewer — open-source vision-model E2E testing harness. Record, replay, heal, and assert with any OpenRouter model under a hard budget cap.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,18 +46,18 @@
|
|
|
46
46
|
"demo": "node scripts/demo.mjs"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@eslint/js": "^
|
|
50
|
-
"@types/node": "^
|
|
49
|
+
"@eslint/js": "^10.0.1",
|
|
50
|
+
"@types/node": "^26.6.2",
|
|
51
51
|
"electron": "^44.2.0",
|
|
52
|
-
"eslint": "^
|
|
53
|
-
"globals": "^
|
|
52
|
+
"eslint": "^10.11.0",
|
|
53
|
+
"globals": "^17.12.0",
|
|
54
54
|
"prettier": "^3.0.0",
|
|
55
55
|
"typescript-eslint": "^8.0.0",
|
|
56
|
-
"vitest": "^
|
|
56
|
+
"vitest": "^5.0.1"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"playwright": "1.63.0",
|
|
60
|
-
"typescript": "^
|
|
60
|
+
"typescript": "^6.0.3"
|
|
61
61
|
},
|
|
62
62
|
"repository": {
|
|
63
63
|
"type": "git",
|