@sreetej510/pi-shipd-checks 0.1.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/LICENSE +21 -0
- package/README.md +104 -0
- package/package.json +48 -0
- package/src/agents.ts +198 -0
- package/src/command.ts +387 -0
- package/src/config.ts +84 -0
- package/src/git.ts +30 -0
- package/src/index.ts +125 -0
- package/src/progress.ts +11 -0
- package/src/prompts.ts +202 -0
- package/src/report.ts +142 -0
- package/src/roles.ts +20 -0
- package/src/rubric.ts +32 -0
- package/src/state.ts +31 -0
- package/src/tools.ts +140 -0
- package/src/types.ts +56 -0
package/src/prompts.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All prompt text sent to the reviewer / gap-finder / gap-validator agents.
|
|
3
|
+
* Keeping this in one file means future prompt tweaks never require touching
|
|
4
|
+
* agents.ts or command.ts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { GAP_FINDER_TOOL_NAME, GAP_VALIDATOR_TOOL_NAME, REPORT_TOOL_NAME } from "./tools.js";
|
|
8
|
+
import type { ReviewerRole, ReviewerRoleKey, TestGapCandidate } from "./types.js";
|
|
9
|
+
|
|
10
|
+
/** Per-role instructions on what to look at and, for `tests`/`solution`, mandatory extra checks. */
|
|
11
|
+
const ROLE_FOCUS: Record<ReviewerRoleKey, string> = {
|
|
12
|
+
description:
|
|
13
|
+
"Focus area: the task description in `agent_prompt.md`. Judge it strictly against rubric items P1-P5 below. " +
|
|
14
|
+
"You do not need to judge the tests or solution — other reviewers cover those.",
|
|
15
|
+
tests:
|
|
16
|
+
"Focus area: the tests added in `test.patch` (a unified diff). Judge them strictly against rubric items T1-T6 below. " +
|
|
17
|
+
"You cannot execute code or apply the patch, so read the diff carefully and reason about determinism, coverage, and " +
|
|
18
|
+
"strictness directly from the added code. Read `agent_prompt.md` for context on what behavior is in scope, and skim " +
|
|
19
|
+
"`solution.patch` only to understand what the tests are checking. Do not judge the description or the solution's code quality.\n" +
|
|
20
|
+
"Mandatory symbol-fairness check: for every non-trivial method/function/property/export name that a test calls, " +
|
|
21
|
+
"mutates, mocks, or asserts on, use grep/read on the repository (the pre-existing code, not solution.patch) to " +
|
|
22
|
+
"confirm it actually exists there, OR confirm it is explicitly named in `agent_prompt.md`. Pay special attention to " +
|
|
23
|
+
"any such name that is new/invented and that duplicates, shadows, or conflicts with an existing, differently-named " +
|
|
24
|
+
"public API doing the same thing (e.g. a test calling `setFoo(...)` when the repo's real, visible API is `setBar(...)`) " +
|
|
25
|
+
"— that is a textbook unfair/undiscoverable test per the fairness methodology, and is blocking on its own even if " +
|
|
26
|
+
"every other test in the patch is fine.",
|
|
27
|
+
solution:
|
|
28
|
+
"Focus area: the golden solution in `solution.patch` (a unified diff). Judge it strictly against rubric items S1-S4 below. " +
|
|
29
|
+
"Read `agent_prompt.md` for the requirements and `test.patch` to see what must pass, and use read/grep/ls/find on the rest " +
|
|
30
|
+
"of the repository to check for regressions, inconsistent style, and irrelevant/unexplained changes. Do not judge the " +
|
|
31
|
+
"description's wording or the tests' coverage.\n" +
|
|
32
|
+
"Mandatory dead-code check: scan every added/changed line in `solution.patch` for unused code — variables, " +
|
|
33
|
+
"parameters, imports, functions/methods, or fields that are declared/assigned but never read or called anywhere " +
|
|
34
|
+
"(including by `test.patch`) — and for dead/unreachable code (branches, conditions, or statements that can never " +
|
|
35
|
+
"execute, or code left behind after a return/throw/break that makes it unreachable). Use grep to confirm a symbol " +
|
|
36
|
+
"truly has no other usages in the repo before flagging it. This is a rubric S4 violation and is blocking on its " +
|
|
37
|
+
"own, even if the rest of the solution is otherwise excellent.",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export function buildReviewerPrompt(role: ReviewerRole, rubric: string, fairnessRules: string): string {
|
|
41
|
+
const parts = [
|
|
42
|
+
"You are a careful, calibrated reviewer for a coding-agent benchmark task.",
|
|
43
|
+
"You are working inside a throwaway, read-only copy of a git repository (this is your current directory). " +
|
|
44
|
+
"You have access to read/grep/find/ls tools only — you cannot execute code, apply patches, or edit files.",
|
|
45
|
+
"The repo root may contain: `agent_prompt.md` (the task description), `test.patch` (unified diff adding tests), " +
|
|
46
|
+
"and `solution.patch` (unified diff of the golden solution). Read whichever are relevant to your focus, and read " +
|
|
47
|
+
"the rest of the repository as needed via grep/read/ls/find to judge things against real repo context.",
|
|
48
|
+
"",
|
|
49
|
+
ROLE_FOCUS[role.key],
|
|
50
|
+
"",
|
|
51
|
+
"Checklist for your focus area:",
|
|
52
|
+
rubric,
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
if (fairnessRules) {
|
|
56
|
+
parts.push(
|
|
57
|
+
"",
|
|
58
|
+
"Fairness methodology (use this to judge whether an issue is actually blocking, and to distinguish agent-fault " +
|
|
59
|
+
"from prompt-ambiguity from test-flaw problems):",
|
|
60
|
+
fairnessRules,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
parts.push(
|
|
65
|
+
"",
|
|
66
|
+
"How to set the verdict — calibrate, don't nitpick:",
|
|
67
|
+
"FAIL only for a genuine BLOCKING issue: a checklist item is clearly violated, a requirement stated in " +
|
|
68
|
+
"`agent_prompt.md` is untested or contradicted, a test asserts something unfair/undiscoverable per the " +
|
|
69
|
+
"fairness methodology above (private internals, exact class names not in the prompt, exact call order, " +
|
|
70
|
+
"reference-solution-only structure, etc.), a test is genuinely non-deterministic in a way that risks real " +
|
|
71
|
+
"CI flakiness (real network calls, unseeded randomness, race-prone ordering), or the solution has a real " +
|
|
72
|
+
"regression, missing requirement, or unrelated/unexplained change.",
|
|
73
|
+
"Do NOT fail for: optional coverage suggestions, 'would also be nice to test X', dead/unused code that doesn't " +
|
|
74
|
+
"affect correctness, minor style inconsistencies, or defensible implementation choices the prompt didn't " +
|
|
75
|
+
"forbid. These are exactly the kind of thing a real reviewer leaves as a 'Minor/optional' note without " +
|
|
76
|
+
"failing the task — put them in `notes` and still return PASS.",
|
|
77
|
+
"When genuinely torn between PASS and FAIL, default to PASS with the concern captured in `notes`, unless the " +
|
|
78
|
+
"issue would let a materially incorrect agent solution pass the hidden tests, or would unfairly fail a " +
|
|
79
|
+
"correct one — that is always blocking.",
|
|
80
|
+
"A single blocking issue is enough to FAIL, even if it affects only one test or one line out of many, and even " +
|
|
81
|
+
"if the rest of the suite is excellent. Do NOT average it away or let a large, otherwise-strong test suite " +
|
|
82
|
+
"talk you into a PASS — one unfair or undiscoverable test (e.g. requiring a private/invented API name that " +
|
|
83
|
+
"doesn't exist in the repo and isn't named in the prompt, especially one that conflicts with an existing, " +
|
|
84
|
+
"differently-named public API) is exactly as blocking as many.",
|
|
85
|
+
"",
|
|
86
|
+
`When you are done analyzing, call the \`${REPORT_TOOL_NAME}\` tool exactly once with your structured verdict. ` +
|
|
87
|
+
"That tool call is your only way to report a result.",
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return parts.join("\n");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function buildGapFinderPrompt(testRubric: string, fairnessRules: string): string {
|
|
94
|
+
const parts = [
|
|
95
|
+
"You are an exhaustive, research-minded test-coverage analyst for a coding-agent benchmark task. Your mandate " +
|
|
96
|
+
"is to dig as deep as possible and surface EVERY genuine behavioral test gap you can find — not just the " +
|
|
97
|
+
"first one or two obvious ones. Treat a short list as a signal that you stopped too early, not as success.",
|
|
98
|
+
"You are working inside a throwaway, read-only copy of a git repository (this is your current directory). " +
|
|
99
|
+
"You have access to read/grep/find/ls tools only — you cannot execute code, apply patches, or edit files.",
|
|
100
|
+
"The repo root contains `agent_prompt.md` (the task description given to a coding agent), `test.patch` (a " +
|
|
101
|
+
"unified diff adding the hidden tests that will grade that agent's solution), and `solution.patch` (a " +
|
|
102
|
+
"unified diff of one golden/reference solution).",
|
|
103
|
+
"",
|
|
104
|
+
"Your job: find real BEHAVIORAL TEST GAPS. A gap is required or clearly-implied behavior from " +
|
|
105
|
+
"`agent_prompt.md` (and, where relevant, obvious existing repo conventions) that `test.patch` does NOT " +
|
|
106
|
+
"actually verify — such that a plausible alternative implementation could satisfy `agent_prompt.md` on its " +
|
|
107
|
+
"face, differ from `solution.patch`, get that behavior wrong or skip it entirely, and STILL pass every test " +
|
|
108
|
+
"in `test.patch` as written.",
|
|
109
|
+
"",
|
|
110
|
+
"Be systematic and exhaustive — do not stop after finding one or two gaps. Work through ALL of the following " +
|
|
111
|
+
"passes before you consider yourself done:",
|
|
112
|
+
"1. Go through `agent_prompt.md` sentence by sentence. For every distinct requirement, constraint, or implied " +
|
|
113
|
+
"rule, explicitly check which test(s) in `test.patch` exercise it, and how thoroughly.",
|
|
114
|
+
"2. Go through `solution.patch` branch by branch — every conditional, loop, early return, error path, and " +
|
|
115
|
+
"state transition. For each one, ask whether `test.patch` actually forces that branch to be taken and its " +
|
|
116
|
+
"outcome checked, or whether an implementation that got that branch wrong would still pass.",
|
|
117
|
+
"3. Systematically consider standard edge-case categories against the required behavior: boundary/limit " +
|
|
118
|
+
"values, empty/missing/null/zero inputs, duplicate or repeated inputs, ordering and interleaving, " +
|
|
119
|
+
"concurrent or repeated invocations, error/failure/rollback paths, interaction between two or more " +
|
|
120
|
+
"required behaviors at once (not just each in isolation), and state left behind after an operation.",
|
|
121
|
+
"4. Cross-check overlapping/interacting requirements — behaviors that are each tested alone but never tested " +
|
|
122
|
+
"together — since that's exactly where a plausible-looking but incomplete implementation slips through.",
|
|
123
|
+
"Do not filter yourself or self-censor for volume. List every gap that survives your own check against the " +
|
|
124
|
+
"ground rules below — a long, thorough list is expected and desired. A separate, independent agent will " +
|
|
125
|
+
"strictly filter this list afterward, so your job here is coverage and recall, not brevity.",
|
|
126
|
+
"",
|
|
127
|
+
"Ground rules — do not overreach:",
|
|
128
|
+
"- Every gap must trace back to a specific requirement or sentence in `agent_prompt.md`, or to behavior that " +
|
|
129
|
+
"is unambiguous from the existing, visible repo. Do not invent requirements the prompt doesn't support.",
|
|
130
|
+
"- Do not propose gaps for things `agent_prompt.md` leaves intentionally open, or for implementation " +
|
|
131
|
+
"details/style the prompt doesn't mandate.",
|
|
132
|
+
"- Read `test.patch` carefully before deciding something is untested — do not propose a gap that an existing " +
|
|
133
|
+
"test already covers (even indirectly).",
|
|
134
|
+
"",
|
|
135
|
+
"For reference, here is the checklist for the tests focus area (use it to calibrate what good coverage looks " +
|
|
136
|
+
"like, not as a list of gaps to report verbatim):",
|
|
137
|
+
testRubric,
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
if (fairnessRules) {
|
|
141
|
+
parts.push("", "Fairness methodology (context on what a fair, in-scope requirement looks like):", fairnessRules);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
parts.push(
|
|
145
|
+
"",
|
|
146
|
+
"For each gap, describe: (1) the specific untested behavior/edge case, in plain terms, and (2) concretely why " +
|
|
147
|
+
"a plausible-but-incorrect implementation would still pass every given test despite missing or " +
|
|
148
|
+
"misimplementing it.",
|
|
149
|
+
`When you are done — after completing ALL the passes above — call the \`${GAP_FINDER_TOOL_NAME}\` tool exactly ` +
|
|
150
|
+
"once with your full candidate list (empty only if, after genuinely exhaustive analysis, none exist). " +
|
|
151
|
+
"That tool call is your only way to report a result.",
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return parts.join("\n");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function buildGapValidatorPrompt(
|
|
158
|
+
candidates: TestGapCandidate[],
|
|
159
|
+
testRubric: string,
|
|
160
|
+
fairnessRules: string,
|
|
161
|
+
): string {
|
|
162
|
+
const parts = [
|
|
163
|
+
"You are a strict, skeptical fairness auditor for a coding-agent benchmark task.",
|
|
164
|
+
"You are working inside a throwaway, read-only copy of a git repository (this is your current directory). " +
|
|
165
|
+
"You have access to read/grep/find/ls tools only — you cannot execute code, apply patches, or edit files.",
|
|
166
|
+
"Another research agent reviewed this same task (`agent_prompt.md`, `test.patch`, `solution.patch`, and the " +
|
|
167
|
+
"repo) and proposed the following CANDIDATE test gaps — behaviors it believes are required but untested:",
|
|
168
|
+
"",
|
|
169
|
+
JSON.stringify(candidates, null, 2),
|
|
170
|
+
"",
|
|
171
|
+
"Your job is to independently re-verify the files yourself and FILTER this list down to only candidates that " +
|
|
172
|
+
"are ALL of the following:",
|
|
173
|
+
"1. Genuinely grounded — actually required by a specific statement in `agent_prompt.md`, or unambiguous from " +
|
|
174
|
+
"clearly visible, existing repo behavior. Drop anything speculative, nice-to-have, or invented beyond what " +
|
|
175
|
+
"the prompt actually asks for.",
|
|
176
|
+
"2. Fair to test — verifying it would not require undiscoverable private internals, an invented/unnamed API, " +
|
|
177
|
+
"or exact incidental structure that only `solution.patch` happens to use. Judge this precisely against the " +
|
|
178
|
+
"fairness methodology below.",
|
|
179
|
+
"3. A real, distinct coverage hole — re-check `test.patch` yourself; drop any candidate an existing test " +
|
|
180
|
+
"already covers, and drop near-duplicate candidates (keep only the clearest phrasing of each distinct gap).",
|
|
181
|
+
"",
|
|
182
|
+
"Be strict: when genuinely unsure whether a candidate holds up, drop it rather than keep it. It is fine — " +
|
|
183
|
+
"expected, even — to return an empty list if none of the candidates survive scrutiny.",
|
|
184
|
+
"",
|
|
185
|
+
"Checklist for the tests focus area, for calibration:",
|
|
186
|
+
testRubric,
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
if (fairnessRules) {
|
|
190
|
+
parts.push("", "Fairness methodology:", fairnessRules);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
parts.push(
|
|
194
|
+
"",
|
|
195
|
+
"For every gap you keep, give a short justification citing where in `agent_prompt.md` or the repo it is " +
|
|
196
|
+
"grounded.",
|
|
197
|
+
`When you are done, call the \`${GAP_VALIDATOR_TOOL_NAME}\` tool exactly once with your final filtered list ` +
|
|
198
|
+
"(which may be empty). That tool call is your only way to report a result.",
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return parts.join("\n");
|
|
202
|
+
}
|
package/src/report.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* shipd_report.json read/merge/summarize logic. Running --review, --description,
|
|
3
|
+
* --tests, --solution, and --gap-finder separately (in any order) should build
|
|
4
|
+
* up one combined report rather than each overwriting the others' results.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { ROLES } from "./roles.js";
|
|
9
|
+
import type { ChecksConfig, ReviewerRole, ReviewReport, TestGapFinal, Verdict } from "./types.js";
|
|
10
|
+
|
|
11
|
+
export const REQUIRED_FILES = ["agent_prompt.md", "solution.patch", "test.patch"] as const;
|
|
12
|
+
|
|
13
|
+
/** Load a prior shipd_report.json (if any) so a later run can merge into it instead of clobbering it. */
|
|
14
|
+
export function loadExistingReport(reportPath: string): Record<string, unknown> {
|
|
15
|
+
try {
|
|
16
|
+
if (!existsSync(reportPath)) return {};
|
|
17
|
+
const parsed = JSON.parse(readFileSync(reportPath, "utf-8"));
|
|
18
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
19
|
+
} catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface MergeReportInput {
|
|
25
|
+
existingReport: Record<string, unknown>;
|
|
26
|
+
config: ChecksConfig;
|
|
27
|
+
runReviewers: boolean;
|
|
28
|
+
byRole: Record<string, ReviewReport>;
|
|
29
|
+
runGapStages: boolean;
|
|
30
|
+
testGaps: TestGapFinal[];
|
|
31
|
+
gapAnalysisIncomplete: boolean;
|
|
32
|
+
gapFinderStatus: string;
|
|
33
|
+
gapFilterStatus: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Merges this run's results into the prior report, keeping any roles/gaps not touched this time. */
|
|
37
|
+
export function mergeReport(input: MergeReportInput): Record<string, unknown> {
|
|
38
|
+
const {
|
|
39
|
+
existingReport,
|
|
40
|
+
config,
|
|
41
|
+
runReviewers,
|
|
42
|
+
byRole,
|
|
43
|
+
runGapStages,
|
|
44
|
+
testGaps,
|
|
45
|
+
gapAnalysisIncomplete,
|
|
46
|
+
gapFinderStatus,
|
|
47
|
+
gapFilterStatus,
|
|
48
|
+
} = input;
|
|
49
|
+
|
|
50
|
+
const merged: Record<string, unknown> = {
|
|
51
|
+
...existingReport,
|
|
52
|
+
timestamp: new Date().toISOString(),
|
|
53
|
+
model: `${config.provider}/${config.modelId}`,
|
|
54
|
+
thinkingLevel: config.thinkingLevel,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (runReviewers) {
|
|
58
|
+
const existingReports =
|
|
59
|
+
existingReport.reports && typeof existingReport.reports === "object"
|
|
60
|
+
? (existingReport.reports as Record<string, ReviewReport>)
|
|
61
|
+
: {};
|
|
62
|
+
const mergedReports = { ...existingReports, ...byRole };
|
|
63
|
+
merged.reports = mergedReports;
|
|
64
|
+
// `overall` only reflects a confident PASS/FAIL once all 3 focus
|
|
65
|
+
// reviewers have actually run (possibly across separate invocations
|
|
66
|
+
// of --description/--tests/--solution) — otherwise it's incomplete.
|
|
67
|
+
if (ROLES.every((role) => mergedReports[role.key])) {
|
|
68
|
+
merged.overall = ROLES.every((role) => mergedReports[role.key].verdict === "PASS") ? "PASS" : "FAIL";
|
|
69
|
+
} else {
|
|
70
|
+
delete merged.overall;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (runGapStages) {
|
|
75
|
+
merged.testGaps = testGaps;
|
|
76
|
+
if (gapAnalysisIncomplete) {
|
|
77
|
+
merged.testGapAnalysisNote = `Gap analysis did not fully complete (finder: ${gapFinderStatus}, filter: ${gapFilterStatus}); testGaps may be incomplete.`;
|
|
78
|
+
} else {
|
|
79
|
+
delete merged.testGapAnalysisNote;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return merged;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface RunSummary {
|
|
87
|
+
content: string;
|
|
88
|
+
details: {
|
|
89
|
+
overall?: Verdict;
|
|
90
|
+
hasTestGaps: boolean;
|
|
91
|
+
gapsCount: number;
|
|
92
|
+
roleVerdicts?: Record<string, Verdict>;
|
|
93
|
+
showGaps: boolean;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Builds the one-line chat summary + renderer details for this run's results. */
|
|
98
|
+
export function buildRunSummary(opts: {
|
|
99
|
+
merged: Record<string, unknown>;
|
|
100
|
+
runReviewers: boolean;
|
|
101
|
+
activeRoles: ReviewerRole[];
|
|
102
|
+
byRole: Record<string, ReviewReport>;
|
|
103
|
+
runGapStages: boolean;
|
|
104
|
+
}): RunSummary {
|
|
105
|
+
const { merged, runReviewers, activeRoles, byRole, runGapStages } = opts;
|
|
106
|
+
|
|
107
|
+
const overall = typeof merged.overall === "string" ? (merged.overall as Verdict) : undefined;
|
|
108
|
+
const gapsCount = Array.isArray(merged.testGaps) ? merged.testGaps.length : 0;
|
|
109
|
+
|
|
110
|
+
// Always surface whichever reviewer(s) actually just ran, even when
|
|
111
|
+
// `overall` is still incomplete (e.g. --tests alone, before --description
|
|
112
|
+
// and --solution have run) — a partial run must never look like it
|
|
113
|
+
// produced no PASS/FAIL information at all.
|
|
114
|
+
const roleVerdicts: Record<string, Verdict> | undefined = runReviewers
|
|
115
|
+
? Object.fromEntries(
|
|
116
|
+
activeRoles.filter((role) => byRole[role.key]).map((role) => [role.key, byRole[role.key].verdict]),
|
|
117
|
+
)
|
|
118
|
+
: undefined;
|
|
119
|
+
|
|
120
|
+
const summaryParts: string[] = [];
|
|
121
|
+
if (roleVerdicts) {
|
|
122
|
+
summaryParts.push(activeRoles.map((role) => `${role.label}: ${roleVerdicts[role.key] ?? "?"}`).join(", "));
|
|
123
|
+
}
|
|
124
|
+
if (overall) {
|
|
125
|
+
const suffix = overall === "PASS" && gapsCount > 0 ? " (with test gaps)" : "";
|
|
126
|
+
summaryParts.push(`Overall: ${overall}${suffix}`);
|
|
127
|
+
}
|
|
128
|
+
if (runGapStages) {
|
|
129
|
+
summaryParts.push(gapsCount > 0 ? `${gapsCount} test gap(s) found` : "no test gaps found");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
content: `Checks: ${summaryParts.join(" | ")}`,
|
|
134
|
+
details: {
|
|
135
|
+
overall,
|
|
136
|
+
hasTestGaps: gapsCount > 0,
|
|
137
|
+
gapsCount,
|
|
138
|
+
roleVerdicts,
|
|
139
|
+
showGaps: runGapStages,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
package/src/roles.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReviewerRole } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/** The 3 focus reviewers. Their prompt text (`focus`) lives in prompts.ts, not here. */
|
|
4
|
+
export const ROLES: ReviewerRole[] = [
|
|
5
|
+
{
|
|
6
|
+
key: "description",
|
|
7
|
+
label: "Description",
|
|
8
|
+
rubricHeading: /^## The problem description/i,
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
key: "tests",
|
|
12
|
+
label: "Tests",
|
|
13
|
+
rubricHeading: /^## The tests/i,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
key: "solution",
|
|
17
|
+
label: "Solution",
|
|
18
|
+
rubricHeading: /^## The solution/i,
|
|
19
|
+
},
|
|
20
|
+
];
|
package/src/rubric.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static rubric text embedded directly in the extension for consistency
|
|
3
|
+
* across machines: the per-focus P1-P5/T1-T6/S1-S4 checklist (was
|
|
4
|
+
* ~/.pi/agent/guidelines.txt) and the fairness methodology — agent-fault vs
|
|
5
|
+
* prompt-ambiguity vs test-flaw, fair/unfair test examples (was
|
|
6
|
+
* ~/.pi/agent/rules.md).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ROLES } from "./roles.js";
|
|
10
|
+
import type { ReviewerRoleKey } from "./types.js";
|
|
11
|
+
|
|
12
|
+
const GUIDELINES_TEXT =
|
|
13
|
+
"## The problem description (aka the task)\r\nP1: Aligns with the repo's philosophy\r\nP2: Self-contained — solvable from the repo and description alone\r\nP3: Clear, concise, and unambiguous — describes what to build or fix and don't leave points for guessing\r\nP4: Verifiable — success is objectively testable\r\nP5: Not prescriptive — don't leak the solution, we want to challenge the agents, remember!!\r\n\r\nA note on how it should read: write it the way a maintainer writes an issue —\r\nnatural prose, full sentences. Open with the ask itself (“Add X to Y”, “Fix Z when …”) and let the first line stand on its own without the title. Skip the motivation and the “what the repo currently lacks” preamble.\r\nKeep it out of spec-sheet territory — no bulleted requirement lists, no headings, no code snippets doing the describing. And don't spell out what a developer in the repo would find on their own (internal class names, helpers, field names, the file layout);\r\ndescribe the behavior, not the implementation. That said, use your judgment\r\n— if a detail is genuinely part of the contract and the task can't be pinned down without it, state it. A task nobody can implement is worse than one that names a field.\r\n\r\n## The tests\r\nYour tests are the backbone; the agent only gets the repo and the problem description and is expected to create a solution to pass your tests (they'll be hidden 🤫). They should follow these requirements:\r\nT1: They highlight the missing or incorrect behavior (depending on the task's category — feature request, bug fix, etc). They should 100% fail at the base commit and 100% pass after adding your solution.\r\nT2: The tests should be deterministic (no timing, randomness, or ordering; nothing that could change across multiple runs or across different machines).\r\nT3: Strong tests 💪. The tests shouldn't be permissive enough to let inaccurate agent solutions pass.\r\nT4: Extensive coverage. The tests should cover the requested behavior and all the obvious edge cases.\r\nT5: DO NOT check for unspecified or undiscoverable behavior — this will be unfair to the agents to expect them to implement something not in the description or discoverable from the repo 😞.\r\nT6: Don't over-pin the output — don't assert on exact output (error text, messages, wording, formatting) unless the description says so or it's obvious from the repo's existing patterns. Otherwise it's unfair to an agent that gets the behavior right but words it differently; checking the behavior holds is enough.\r\n\r\n## The solution\r\nThat's the golden solution — the proof that your task is solvable and that we can pass the tests. The solution should follow these requirements:\r\nS1: The solution should meet all the requirements. (If it's missing a requirement and it manages to pass your tests, you're in a bad position 😠.)\r\nS2: No regressions and follow existing code patterns. Don't break existing working code by mistake (we'll still run the repo's existing tests).\r\nS3: No irrelevant changes to the code — if something is unrelated to your task, keep it as it is.\r\nS4: No AI slop (weird comments, unexplained defensive code, new coding patterns, etc).";
|
|
14
|
+
|
|
15
|
+
/** Full fairness methodology (embedded — was ~/.pi/agent/rules.md): agent-fault vs prompt-ambiguity vs test-flaw, fair/unfair examples. */
|
|
16
|
+
const RULES_TEXT =
|
|
17
|
+
"# Agent Evaluation Guidelines\r\n\r\nThis file describes how to align a task prompt, reference solution, and tests for fair coding-agent evaluation. It is generic and can be reused across repositories and tasks.\r\n\r\n## Core Principle\r\n\r\nThe prompt, solution, and tests should form a consistent triangle:\r\n\r\n- **Prompt**: defines required behavior and public contracts.\r\n- **Solution**: demonstrates one valid implementation.\r\n- **Tests**: verify the prompt requirements, not incidental choices from the solution.\r\n\r\nA test is fair only when it checks behavior required by the prompt, strongly implied by existing code, or exposed through a public API named in the prompt.\r\n\r\nA useful mental model is: **test what the user, caller, or public contract can observe; avoid testing how the implementation got there unless that mechanism is itself part of the contract.**\r\n\r\n---\r\n\r\n## Prompt Rules\r\n\r\nThe task prompt should be written in paragraphs, not bullet points or numbered lists, should be valid UTF-8. Keep it concise with a soft maximum of about 300 words, unless the task truly requires more detail to be fair.\r\n\r\nDo not restate the same requirement in multiple ways. Do not include long explanations for why a requirement exists. State the required behavior, public API, data shape, selector, or side effect clearly and move on.\r\n\r\nInclude implementation details only when tests or compatibility require them. If a selector, translation key, exported function name, file path, data attribute, timing guarantee, option ID, or confirmation message is required by tests, name it explicitly in the prompt. If multiple implementations should be valid, describe the required outcome rather than one mechanism.\r\n\r\n# 1. Early Stage: No Agent Evaluations Yet\r\n\r\nUse this section while creating or refining the problem before agent runs exist.\r\n\r\n## 1.1 Prompt-to-Test Mapping\r\n\r\nFor every test, identify the prompt sentence that justifies it.\r\n\r\nIf no prompt sentence exists, either:\r\n\r\n- add the requirement to the prompt, or\r\n- remove/relax the test.\r\n\r\nTests may also rely on existing public repo behavior, but avoid relying on private internals unless the task is explicitly about those internals.\r\n\r\n## 1.2 Solution Is Not the Spec\r\n\r\nThe reference solution is only one valid implementation. Do not copy its incidental choices into tests unless the prompt requires them.\r\n\r\nAvoid testing:\r\n\r\n- exact local helper names\r\n- exact component file paths\r\n- exact DOM nesting\r\n- exact CSS classes\r\n- exact call order\r\n- exact framework implementation style\r\n- exact module boundaries when multiple placements are valid\r\n- exact translation key names when only visible text matters\r\n- exact callback return shape when only the eventual effect matters\r\n- exact registry keys, menu IDs, or config entry names when only the visible action matters\r\n- one specific state source when multiple repo-valid sources expose the same data\r\n- one specific internal call path when another repo-valid path produces the same public effect\r\n\r\nPrefer testing:\r\n\r\n- public exports\r\n- rendered behavior\r\n- visible labels/text\r\n- required data attributes\r\n- state changes\r\n- side effects\r\n- user-triggered actions\r\n\r\n## 1.3 Public Interface Rules\r\n\r\nA test may assert an API name if:\r\n\r\n- the prompt names it, or\r\n- it already exists as a public API and the task says to use it.\r\n\r\nA test should not assert private helper names introduced only by the reference solution.\r\n\r\nIf a new exported API is required, name it in the prompt.\r\n\r\n## 1.4 Selector and UI Rules\r\n\r\nIf tests need a selector or DOM hook, mention it in the prompt.\r\n\r\nFair required hooks only user visible text include:\r\n\r\n- exact visible labels\r\n- exact `title`\r\n- repo covention common class\r\n\r\nUnfair unless specified:\r\n\r\n- requiring test or data attributes\r\n- requiring `<button>` instead of clickable elements\r\n- requiring `.btn`, `.ctrl`, `.popup-btn`, etc, unless repo convention.\r\n- requiring a row class like `.item`, `.tab`, `.entry`\r\n- requiring specific DOM nesting\r\n\r\nWhen testing controls, find them by required user-facing contract, not style-only class names. If broad discovery is necessary, keep the final assertion strict: exact label/key plus correct behavior.\r\n\r\n## 1.5 Async and Refresh Rules\r\n\r\nIf the prompt says something refreshes or updates after an operation, allow reasonable async timing unless synchronous behavior is explicitly required.\r\n\r\nUse polling helpers or fake timers as appropriate. Do not fail valid debounced or event-driven implementations unless the prompt requires immediate state.\r\n\r\nAvoid requiring refresh to happen inside a specific wrapper if it could fairly happen through a delegated operation, event handler, or shared service.\r\n\r\nDo not assume that an event handler or callback must return a promise unless the prompt or existing public API requires that contract. If the required behavior is eventual, trigger the callback and then flush or poll for the resulting effect.\r\n\r\n## 1.6 Fixture and Mock Rules\r\n\r\nMocks should preserve real code invariants.\r\n\r\nIf real code uses both a list and an ID map, populate both. If real code uses both native and reactive fields, set both when relevant.\r\n\r\nPrefer partial mocks over full replacement when the implementation may reasonably use additional existing exports from the same module. Full mocks are appropriate only when the hidden test truly intends to forbid every other interaction with that module.\r\n\r\nWhen a mocked module exports mutable state, prefer mutating existing arrays and objects in place rather than reassigning them. Reassignment can break valid implementations that hold live references to the original exported values.\r\n\r\nMocks should not remove valid implementation paths. For example:\r\n\r\n- do not mock away a delegated helper and then require the delegated side effect\r\n- do not stub the child component if testing behavior rendered by the child\r\n- include framework probe fields in module mocks when templates need them\r\n- do not replace a service module so narrowly that a valid implementation fails only because it uses another existing export from that module\r\n- do not observe only one internal call path when the same user-visible effect can happen through another repo-valid path\r\n- do not make one mocked function call the only accepted proof that an effect happened if the same effect can be observed through another repo-valid mechanism\r\n\r\nWhen testing text, prefer real dictionaries or neutral translation stubs that resolve the visible strings. Do not make tests pass or fail based on arbitrary translation key naming unless the prompt explicitly names the key.\r\n\r\n## 1.7 Early-Stage Checklist\r\n\r\nBefore running agents, verify:\r\n\r\n- every test maps to prompt text or existing public behavior\r\n- every required selector/key/attribute is named in the prompt\r\n- tests accept valid alternative implementations\r\n- tests avoid private internals\r\n- async behavior is waited for fairly\r\n- fixtures match real runtime invariants\r\n- solution-specific structure is not treated as mandatory\r\n- Keep the prompt behavioral and public-facing; avoid internal API coupling.\r\n- Fairness matters: tests should avoid implementation details and brittle DOM assumptions.\r\n- Prefer existing repo patterns/components (TextInput, ToggleInput, SetupPage.registerEl, debounced saves).\r\n- Preserve baseline behavior; new tests must fail before the feature and pass after.\r\n- Sandbox should be limited/approved, not broad live-app access.\r\n- If tests assert specific visible text, that text must be named in the prompt.\r\n- Avoid DOM class selectors or custom attributes unless explicitly stated in the prompt.\r\n\r\n---\r\n\r\n# 2. After Agent Evaluations Exist\r\n\r\nUse this section when agent runs, solution patches, or failure reports are available.\r\n\r\n## 2.1 Analyze Before Changing Tests\r\n\r\nFor each failed agent:\r\n\r\n1. List failed tests.\r\n2. Inspect the agent's solution patch.\r\n3. Compare the implementation to the prompt, not just the reference solution.\r\n4. Decide whether the failure is agent fault, prompt ambiguity, or test flaw.\r\n\r\nDo not loosen tests merely to make agents pass. Relax tests only when the failed implementation is genuinely valid under the prompt.\r\n\r\n## 2.2 Agent Fault\r\n\r\nKeep the test unchanged when the agent missed a clear requirement.\r\n\r\nAgent fault examples:\r\n\r\n- prompt names a required public API and it is missing\r\n- prompt requires loading data and the implementation does not load it\r\n- prompt requires confirmation and implementation skips it\r\n- prompt requires refresh after an operation and implementation never refreshes\r\n- prompt requires a visible/data attribute and it is absent\r\n- prompt requires a user action to work and it does not work\r\n\r\nRepeated failures are not automatically test flaws. If several agents miss the same explicit requirement, the task may simply be challenging.\r\n\r\n## 2.3 Prompt Ambiguity\r\n\r\nChange the prompt or tests when multiple reasonable interpretations exist.\r\n\r\nAmbiguity examples:\r\n\r\n- tests require logic to live in a specific layer, but prompt only requires outcome\r\n- tests require synchronous behavior, but prompt allows debounced/async behavior\r\n- tests require direct platform calls, but a public helper is equally valid\r\n- tests require a specific control class, but prompt only requires a visible action\r\n- tests assert exact call shape, but prompt only requires the effect\r\n- tests assume a callback is awaitable, but the visible contract only requires eventual behavior\r\n- tests assume one service or module boundary, but the repo allows multiple equivalent integration points\r\n- tests assume one translation key pattern, but the prompt only requires visible text\r\n- tests assume one exact exported mutable object identity, but a valid implementation reads the same public state through a preserved reference\r\n- tests treat one mocked call as the only accepted proof of behavior, but the same visible effect can be observed another way\r\n\r\nFix ambiguity by either:\r\n\r\n- clarifying the prompt, if the detail is truly required, or\r\n- relaxing the test, if multiple implementations should be accepted.\r\n\r\n## 2.4 Test Flaw\r\n\r\nFix the test when it enforces an undocumented implementation detail.\r\n\r\nTest flaw examples:\r\n\r\n- exact CSS class names not in the prompt\r\n- exact DOM hierarchy not in the prompt\r\n- importing private files not named in the task\r\n- mocks inconsistent with real app state\r\n- mocked helpers hide valid delegated behavior\r\n- tests pass only because the reference solution used a specific structure\r\n- translation stubs that only recognize reference-solution key names\r\n- full module mocks that omit repo-valid exports a reasonable solution may use\r\n- reassigning mocked exported state in a way that breaks valid consumers of the original exported references\r\n- asserting one mocked internal function was called when the same visible effect could be produced through another public or repo-valid path\r\n- awaiting a callback and treating resolution timing as a requirement when the contract only promises the eventual side effect\r\n- requiring one exact config entry name, option ID, or registry key when the prompt only requires the visible capability\r\n\r\n## 2.6 Evaluation Checklist\r\n\r\nFor every failure cluster, answer:\r\n\r\n- Is this behavior named in the prompt?\r\n- Is the tested interface public?\r\n- Is the test checking outcome or implementation?\r\n- Could another reasonable implementation satisfy the prompt but fail this test?\r\n- Did mocks/fixtures remove a valid path?\r\n- Is the reference solution being treated as the only solution?\r\n\r\n---\r\n\r\n# 3. Fair vs Unfair Examples\r\n\r\n## Fair\r\n\r\n- Required exported function exists.\r\n- Required state includes specified fields.\r\n- Required UI appears only under specified conditions.\r\n- Required data attribute is present.\r\n- Required control label/key appears and triggers the required action.\r\n- Required confirmation appears before destructive action.\r\n- Required state refresh eventually occurs after operation.\r\n- A callback or user action eventually produces the required side effect, regardless of whether the implementation returns a promise.\r\n- A translated visible string appears, regardless of which internal translation key produced it.\r\n- A feature works through any repo-valid integration point that satisfies the prompt.\r\n- A mock preserves enough of the real module shape that reasonable uses of existing exports still work.\r\n\r\n## Unfair\r\n\r\n- Requires a private helper name.\r\n- Requires a particular component file path not specified.\r\n- Requires direct browser/platform API call when helper delegation is valid.\r\n- Requires exact class names used by the reference solution only.\r\n- Requires immediate update when debounced update is valid.\r\n- Requires exact function call arity when only message/effect matters.\r\n- Uses incomplete fixtures that break valid code paths.\r\n- Requires exact translation key names when the prompt only names the visible text.\r\n- Requires one mocked service export to be called when another repo-valid path produces the same effect.\r\n- Requires a callback to be awaitable or fully settled on return when the visible contract does not say so.\r\n- Requires one exact option ID or registry key when the prompt only requires the user-visible action.\r\n- Reassigns mocked exported state and then blames the implementation for not following the new object identity.\r\n- Treats one internal observation point as mandatory even though the same required outcome can be observed through another public or repo-valid path.\r\n\r\n---\r\n\r\n# 4. Patch Hygiene\r\n\r\nKeep changes separated when possible:\r\n\r\n- **Test patch**: tests, test setup, test runner/config.\r\n- **Solution patch**: implementation, styles, locales, app config.\r\n- Avoid unrelated dependency or lockfile changes.\r\n- Regenerate patches after every prompt/test/solution change.\r\n\r\n---";
|
|
18
|
+
|
|
19
|
+
export function loadGuidelinesSections(): Record<ReviewerRoleKey, string> {
|
|
20
|
+
const parts = GUIDELINES_TEXT.split(/\r?\n(?=## )/g);
|
|
21
|
+
const sections: Partial<Record<ReviewerRoleKey, string>> = {};
|
|
22
|
+
for (const role of ROLES) {
|
|
23
|
+
const match = parts.find((p) => role.rubricHeading.test(p.trim()));
|
|
24
|
+
sections[role.key] = (match ?? GUIDELINES_TEXT).trim();
|
|
25
|
+
}
|
|
26
|
+
return sections as Record<ReviewerRoleKey, string>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Full fairness methodology (agent-fault vs prompt-ambiguity vs test-flaw, fair/unfair examples). */
|
|
30
|
+
export function loadFairnessRules(): string {
|
|
31
|
+
return RULES_TEXT.trim();
|
|
32
|
+
}
|
package/src/state.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module-level run state shared between the /checks command handler and the
|
|
3
|
+
* cancel shortcut — encapsulated behind functions so both sides stay in sync
|
|
4
|
+
* without either needing to reassign another module's bindings directly.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
let reviewInProgress = false;
|
|
8
|
+
let currentReviewAbort: AbortController | undefined;
|
|
9
|
+
|
|
10
|
+
export function isReviewInProgress(): boolean {
|
|
11
|
+
return reviewInProgress;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Marks a run as started and returns the AbortController to pass down to the agents. */
|
|
15
|
+
export function startReview(): AbortController {
|
|
16
|
+
reviewInProgress = true;
|
|
17
|
+
currentReviewAbort = new AbortController();
|
|
18
|
+
return currentReviewAbort;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function endReview(): void {
|
|
22
|
+
reviewInProgress = false;
|
|
23
|
+
currentReviewAbort = undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Returns true if a cancel was actually requested (i.e. a run was in progress and not already cancelled). */
|
|
27
|
+
export function cancelReview(): boolean {
|
|
28
|
+
if (!reviewInProgress || !currentReviewAbort || currentReviewAbort.signal.aborted) return false;
|
|
29
|
+
currentReviewAbort.abort();
|
|
30
|
+
return true;
|
|
31
|
+
}
|
package/src/tools.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom tools the review/gap agents call to submit their structured results.
|
|
3
|
+
* Each tool just writes into a `capture` object passed in by the caller —
|
|
4
|
+
* that's how agents.ts pulls the final result back out of the agent session.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { Type } from "typebox";
|
|
9
|
+
import type { ReviewReport, TestGapCandidate, TestGapFinal } from "./types.js";
|
|
10
|
+
|
|
11
|
+
export const REPORT_TOOL_NAME = "submit_review_report";
|
|
12
|
+
export const GAP_FINDER_TOOL_NAME = "submit_test_gap_candidates";
|
|
13
|
+
export const GAP_VALIDATOR_TOOL_NAME = "submit_filtered_test_gaps";
|
|
14
|
+
|
|
15
|
+
// ── Reviewer report tool ─────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const reportToolParams = Type.Object({
|
|
18
|
+
verdict: Type.Union([Type.Literal("PASS"), Type.Literal("FAIL")], {
|
|
19
|
+
description:
|
|
20
|
+
"FAIL only for a genuine blocking issue (clear rubric violation, agent-fault-worthy gap, unfair/undiscoverable " +
|
|
21
|
+
"test requirement, or a real determinism/regression risk). Optional, minor, or stylistic points are NOT grounds " +
|
|
22
|
+
"for FAIL — put those in `notes` instead and use PASS.",
|
|
23
|
+
}),
|
|
24
|
+
summary: Type.String({ description: "One short sentence summarizing the verdict." }),
|
|
25
|
+
reasons: Type.Array(Type.String(), {
|
|
26
|
+
description:
|
|
27
|
+
"Specific BLOCKING justifications only, citing rubric item IDs and concrete evidence from the files you read. " +
|
|
28
|
+
"Required (non-empty) when verdict is FAIL. Use an empty array when verdict is PASS.",
|
|
29
|
+
}),
|
|
30
|
+
notes: Type.Array(Type.String(), {
|
|
31
|
+
description:
|
|
32
|
+
"Non-blocking, optional/minor observations or suggested improvements — the kind of feedback a real reviewer " +
|
|
33
|
+
"leaves as 'Minor/optional' without failing the task. Include these regardless of verdict; use an empty array " +
|
|
34
|
+
"if you truly have none.",
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export function createReportTool(capture: { report?: ReviewReport }): ToolDefinition<typeof reportToolParams> {
|
|
39
|
+
return {
|
|
40
|
+
name: REPORT_TOOL_NAME,
|
|
41
|
+
label: "Submit Review Report",
|
|
42
|
+
description:
|
|
43
|
+
"Submit your final verdict for this review. This is the ONLY way to report your result — " +
|
|
44
|
+
"call it exactly once, as your last action, after you have finished reading and analyzing the relevant files. " +
|
|
45
|
+
"Do not write a plain-text final answer instead of calling this tool.",
|
|
46
|
+
parameters: reportToolParams,
|
|
47
|
+
async execute(_toolCallId, params) {
|
|
48
|
+
capture.report = {
|
|
49
|
+
verdict: params.verdict,
|
|
50
|
+
summary: params.summary,
|
|
51
|
+
reasons: params.reasons ?? [],
|
|
52
|
+
notes: params.notes ?? [],
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: `Report recorded: ${params.verdict}` }],
|
|
56
|
+
details: undefined,
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── Test-gap finder / filter tools ────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
const gapFinderToolParams = Type.Object({
|
|
65
|
+
gaps: Type.Array(
|
|
66
|
+
Type.Object({
|
|
67
|
+
description: Type.String({ description: "The specific untested behavior or edge case, in plain terms." }),
|
|
68
|
+
risk: Type.String({
|
|
69
|
+
description:
|
|
70
|
+
"Concretely why a plausible-but-incorrect implementation could still pass every test in test.patch " +
|
|
71
|
+
"despite missing or misimplementing this behavior.",
|
|
72
|
+
}),
|
|
73
|
+
}),
|
|
74
|
+
{
|
|
75
|
+
description:
|
|
76
|
+
"Candidate behavioral test gaps grounded in agent_prompt.md or clear existing repo behavior. Use an empty " +
|
|
77
|
+
"array if you found none — do not manufacture gaps just to report something.",
|
|
78
|
+
},
|
|
79
|
+
),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export function createGapFinderTool(capture: {
|
|
83
|
+
gaps?: TestGapCandidate[];
|
|
84
|
+
}): ToolDefinition<typeof gapFinderToolParams> {
|
|
85
|
+
return {
|
|
86
|
+
name: GAP_FINDER_TOOL_NAME,
|
|
87
|
+
label: "Submit Candidate Test Gaps",
|
|
88
|
+
description:
|
|
89
|
+
"Submit your candidate list of behavioral test gaps. This is the ONLY way to report your result — call it " +
|
|
90
|
+
"exactly once, as your last action, after you have finished reading and analyzing the relevant files.",
|
|
91
|
+
parameters: gapFinderToolParams,
|
|
92
|
+
async execute(_toolCallId, params) {
|
|
93
|
+
const gaps = params.gaps ?? [];
|
|
94
|
+
capture.gaps = gaps;
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: "text", text: `Recorded ${gaps.length} candidate gap(s)` }],
|
|
97
|
+
details: undefined,
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const gapValidatorToolParams = Type.Object({
|
|
104
|
+
gaps: Type.Array(
|
|
105
|
+
Type.Object({
|
|
106
|
+
description: Type.String({ description: "The confirmed, real, fair test gap (may be reworded for clarity)." }),
|
|
107
|
+
justification: Type.String({
|
|
108
|
+
description:
|
|
109
|
+
"Why this is genuinely grounded in agent_prompt.md or the repo, fair to test per the fairness " +
|
|
110
|
+
"methodology, and a real (non-duplicate) coverage hole in test.patch.",
|
|
111
|
+
}),
|
|
112
|
+
}),
|
|
113
|
+
{
|
|
114
|
+
description:
|
|
115
|
+
"The filtered, final list of confirmed test gaps. Use an empty array if none of the candidates survive " +
|
|
116
|
+
"strict scrutiny.",
|
|
117
|
+
},
|
|
118
|
+
),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export function createGapValidatorTool(capture: {
|
|
122
|
+
gaps?: TestGapFinal[];
|
|
123
|
+
}): ToolDefinition<typeof gapValidatorToolParams> {
|
|
124
|
+
return {
|
|
125
|
+
name: GAP_VALIDATOR_TOOL_NAME,
|
|
126
|
+
label: "Submit Filtered Test Gaps",
|
|
127
|
+
description:
|
|
128
|
+
"Submit your final, strictly filtered list of confirmed test gaps. This is the ONLY way to report your " +
|
|
129
|
+
"result — call it exactly once, as your last action, after you have independently verified each candidate.",
|
|
130
|
+
parameters: gapValidatorToolParams,
|
|
131
|
+
async execute(_toolCallId, params) {
|
|
132
|
+
const gaps = params.gaps ?? [];
|
|
133
|
+
capture.gaps = gaps;
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: `Confirmed ${gaps.length} gap(s) after filtering` }],
|
|
136
|
+
details: undefined,
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the shipd-checks extension. Kept dependency-free (no
|
|
3
|
+
* imports from pi/typebox) so every other module can import from here
|
|
4
|
+
* without pulling in extra runtime surface.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | "off";
|
|
8
|
+
|
|
9
|
+
export type Verdict = "PASS" | "FAIL";
|
|
10
|
+
|
|
11
|
+
export interface ChecksConfig {
|
|
12
|
+
provider: string;
|
|
13
|
+
modelId: string;
|
|
14
|
+
thinkingLevel: ThinkingLevel;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ReviewReport {
|
|
18
|
+
verdict: Verdict;
|
|
19
|
+
summary: string;
|
|
20
|
+
/** Blocking justifications. Required (non-empty) when verdict is FAIL, empty when PASS. */
|
|
21
|
+
reasons: string[];
|
|
22
|
+
/** Non-blocking, optional/minor suggestions — present regardless of verdict. */
|
|
23
|
+
notes: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A candidate behavioral test gap proposed by the (unfiltered) gap-finder researcher agent. */
|
|
27
|
+
export interface TestGapCandidate {
|
|
28
|
+
description: string;
|
|
29
|
+
risk: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A gap that survived the strict fairness-filter agent — goes into the final report. */
|
|
33
|
+
export interface TestGapFinal {
|
|
34
|
+
description: string;
|
|
35
|
+
justification: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type ReviewerRoleKey = "description" | "tests" | "solution";
|
|
39
|
+
|
|
40
|
+
export interface ReviewerRole {
|
|
41
|
+
key: ReviewerRoleKey;
|
|
42
|
+
label: string;
|
|
43
|
+
rubricHeading: RegExp;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** `ok` means the agent actually finished and submitted (possibly an empty list on purpose). */
|
|
47
|
+
export interface GapStageResult<T> {
|
|
48
|
+
status: "ok" | "timedOut" | "cancelled" | "error" | "noSubmission";
|
|
49
|
+
gaps: T[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CommandOption {
|
|
53
|
+
value: string;
|
|
54
|
+
label: string;
|
|
55
|
+
description: string;
|
|
56
|
+
}
|