Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/e2e/e2e-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,19 @@ type ProcessorTargets = {
* processor without wiring it into the matrix (or dropping one) fails CI instead
* of silently eroding coverage.
*
* The declared targets (from `getToolTargets`) must partition exactly into
* The declared targets (from `getToolTargets`) must be covered by the union of
* `testedTargets` (tools with an entry in the matrix `it.each` dictionary) and
* `untested` (tools intentionally excluded from this matrix — e.g. tools whose
* output only exists in another scope, or that merge into a shared file). Every
* excluded tool must be listed explicitly with a reason so the omission is a
* conscious decision rather than an accidental gap.
*
* `testedTargets` and `untested` must be disjoint: a tool cannot be both tested
* and intentionally untested. (A tool may legitimately appear more than once
* *within* `testedTargets` when it is exercised by several matrices — e.g. a
* tool that emits both a root `AGENTS.md` and a nested tree — so duplicates
* within a single side are allowed.)
*
* `-legacy` targets are dropped from the comparison: they are duplicate aliases
* that the same tables/generators exclude, and are never exercised end-to-end.
*
Expand All @@ -200,6 +206,15 @@ export function assertGenerateMatrixCoversTargets({
.filter((target) => !target.endsWith("-legacy"));
const declaredSet = new Set<string>(declared);

// `testedTargets` and `untested` must be disjoint — a tool listed as both
// tested and intentionally untested is a contradiction the stray/uncovered
// checks below cannot catch (each set covers it, so neither fires).
const overlap = testedTargets.filter((t) => untested.includes(t)).toSorted();
expect(
overlap,
`These targets are listed in both \`testedTargets\` and \`untested\` (a target cannot be both tested and intentionally untested): ${overlap.join(", ")}`,
).toEqual([]);

const stray = [...testedTargets, ...untested].filter((t) => !declaredSet.has(t)).toSorted();
expect(
stray,
Expand Down
7 changes: 6 additions & 1 deletion src/e2e/e2e-hooks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ function assertHookCommandsPreserved(parsed: { hooks?: unknown }): void {
// Tools whose event mapping/serialization needs a
// bespoke assertion (vibe, devin, reasonix) live in their own standalone `it`s
// below; `hooksProjectStandaloneTargets` lists them so the completeness check
// still accounts for them.
// still accounts for them. The check only enforces that this enumeration matches
// the processor's declared target set — it does NOT verify a matching standalone
// `it` exists for each name, so keep this list in sync with the actual `it`s by hand.
const hooksGenerateTargets = [
{ target: "claudecode", outputPath: join(".claude", "settings.json") },
{ target: "cursor", outputPath: join(".cursor", "hooks.json") },
Expand Down Expand Up @@ -576,6 +578,9 @@ const hooksGlobalTargets = [
] as const;

// Global targets exercised by dedicated `it`s (bespoke per-tool serialization).
// As with the project-scope list, the completeness check only enforces that this
// enumeration matches the processor's declared set — not that a matching `it`
// exists for each name; keep it in sync with the actual `it`s by hand.
const hooksGlobalStandaloneTargets = ["devin", "vibe", "hermesagent", "reasonix"] as const;

describe("E2E: hooks (global mode)", () => {
Expand Down
7 changes: 5 additions & 2 deletions src/e2e/e2e-permissions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ import {

// Permissions targets exercised by the project-scope generate `it`s below. Each
// tool has a bespoke serialization, so tests stay hand-written rather than
// table-driven; this explicit list feeds the completeness check so a new
// project-scope permissions tool cannot be added without a matching e2e test.
// table-driven; this explicit list feeds the completeness check. Note the check
// only enforces that this enumeration matches the processor's declared target
// set — it does NOT verify that a dedicated `it` body exists for each name, so a
// tool's `it` could be deleted while its name lingers here and the check stays
// green. Keep this list in sync with the actual `it`s by hand.
const permissionsGenerateTargets = [
"opencode",
"zed",
Expand Down
Loading