Skip to content
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
7fd1e90
Kill the api-keys and attributes mutation survivors
claude Jul 30, 2026
6a6fe10
Answer the review: move the CRUD cases, split the suite, make inserts…
claude Jul 30, 2026
707a48e
Kill the join, i18n, rebrand, and remaining api-keys survivors
claude Jul 30, 2026
1b2d2a1
Kill the attributes tab, option edit/move, and database-reset survivors
claude Jul 30, 2026
b57c9b4
Pin the site forms' labels, hints, and markdown affordance
claude Jul 30, 2026
21ea8a8
Answer the review, unify the ordered inserts, kill the last survivors
claude Jul 30, 2026
b5c2565
Import the client statically in the trigger helper
claude Jul 30, 2026
dbd1025
Follow inputNamed's wider contract in its own test
claude Jul 30, 2026
52910d3
Answer the review: sentence case, named contract, splits, rollback tests
claude Jul 30, 2026
9528978
Pin field schemas, split the site suite, and shard the equivalent reg…
claude Jul 30, 2026
596ce08
Mirror the answer tests to the split answers module
claude Jul 30, 2026
dd448a9
Pin the field input metadata and the builder and autofocus views
claude Jul 30, 2026
32c2aed
Answer the second review round: registry hygiene, typed contract, sen…
claude Jul 30, 2026
93c6dba
Join the not-found import onto one line for the formatter
claude Jul 30, 2026
b70bd07
Finish the sentence-case sweep and preflight the registry copy-back
claude Jul 30, 2026
2ec5a06
Give the listing-choice factory its own mirror test
claude Jul 30, 2026
606d218
Mirror-test the id-table wrapper
claude Jul 30, 2026
27c7203
Refresh shifted registry records, recheck copy-back at write time, No…
claude Jul 30, 2026
db8219e
Keep the No check-in rename out of code comments
claude Jul 30, 2026
c985d99
Tell the questions, details, and front-page stories in Cucumber
claude Jul 30, 2026
1813dcf
Merge origin/main, keeping the curried take-down helper
claude Jul 30, 2026
d669a9f
Keep the owner-was-told helper with the story support only stories load
claude Jul 30, 2026
1f58f63
Full stops on the duration errors, and the free-text refusal in the c…
claude Jul 30, 2026
c0cc322
Sentence-case the site editor headings, refresh table records, roll b…
claude Jul 30, 2026
c4506a1
Share the two-file setup between the copy-back failure tests
claude Jul 30, 2026
cb80ec6
Sentence-case the question and attribute buttons, keep mid-run edits …
claude Jul 30, 2026
8c5702f
Word the site editor forms from the catalog, finish the listing-type …
claude Jul 30, 2026
5ee84cc
Address the CodeRabbit round: casing sweep, sturdier undo, stricter h…
claude Jul 30, 2026
adf3ace
Report undo problems instead of throwing them
claude Jul 30, 2026
85a9d20
Hand the snapshot child the parent's registry list, refresh 27 records
claude Jul 30, 2026
da36269
Share one flag-splitting helper between the spec and audit commands
claude Jul 30, 2026
8463395
Read the builder and listing-filter copy from the catalog, drop a don…
claude Jul 30, 2026
5a33bdf
One mechanism for the shared built-site boxes
claude Jul 30, 2026
9f15574
Pin the payments guide schema with its own direct suite
claude Jul 30, 2026
1e915f2
Tighten the flag parsing, control matching, and refusal assertions
claude Jul 30, 2026
89e0a47
Prove the ticked box is really offered, finish three copy edges
claude Jul 30, 2026
2e81e83
Send stories through the served forms, catalog the last touched copy
claude Jul 30, 2026
9b5e2b8
Commit the lock update for @std/csv
claude Jul 30, 2026
3b82cfe
Cover both arms of the form-preference choice directly
claude Jul 30, 2026
730c254
Refuse a hand-passed --registry in the audit parent
claude Jul 30, 2026
d8a843b
Kill the gate's nineteen survivors: tighter pins and five equivalents
claude Jul 30, 2026
1748578
Catalog the provider labels, bound the field ranking, drop optional c…
claude Jul 30, 2026
79bbcfd
Sentence-case the builder, logistics, questions, join, setup, and hol…
claude Jul 30, 2026
e7a6912
Sentence-case the last Title Case labels on the touched pages
claude Jul 30, 2026
e1fdb04
Match the Other attendees pins to the renamed heading
claude Jul 30, 2026
c63fa7d
Finish the agreement error, write copy-backs whole or not at all
claude Jul 30, 2026
4fdf504
Make every registry write whole-or-nothing, tick the real order box
claude Jul 30, 2026
28c2a21
Name the registry file path once, string or URL alike
claude Jul 30, 2026
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
10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,7 @@ rate, so the cost stays bounded to the source files you actually changed. Run
branch that changes `src/` files; the standard `deno task precommit` no longer
runs it (it was too slow for every commit).
Known-equivalent survivors recorded in
`scripts/mutation/equivalent-mutants.txt` are suppressed, as with a manual run.
`scripts/mutation/equivalent-mutants/` are suppressed, as with a manual run.
Never record `=== → ==`/`!== → !=` mutants: Biome's `noDoubleEquals` rule is
configured to reject loose comparisons even against `null`, and the runner
counts that lint failure as killed before tests run. Use
Expand All @@ -980,8 +980,8 @@ now kill. The audit never runs tests and refuses to rewrite stale or malformed
entries. Like `deno task mutation`, it works in a copy of the checkout under
`.mutation-runs/`, so the live source files are never left mutated and a commit
made while it runs cannot pick up a mutant. With `--write`, the pruned
`equivalent-mutants.txt` is copied back when the run ends — unless that file was
edited meanwhile, which fails the run instead of overwriting the edit.
`equivalent-mutants/` registry files are copied back when the run ends — unless
one was edited meanwhile, which fails the run instead of overwriting the edit.

Before it runs the mapped tests, the runner puts every mutant through two cheap
**static gates**, ordered cheapest-first: a per-file Biome **lint** and then a
Expand All @@ -998,7 +998,7 @@ trusted after the runner confirms the *unmutated* target passes it (the baseline
probe): a standalone `deno task mutation` doesn't run `lint:ci`/`typecheck`
first, so if the target isn't already clean the run aborts loudly rather than
scoring a bogus 100%. This means a mutant recorded in
`equivalent-mutants.txt` must be one that survives *both* gates *and* the tests;
`equivalent-mutants/` must be one that survives *both* gates *and* the tests;
a mutation that produces a type error never reaches the ignore-list because the
type-check gate kills it first.

Expand All @@ -1008,7 +1008,7 @@ to fix. Never determine whether a survivor "predates main" (no `git stash`, no
diffing against the base to excuse it): the bar is 100%, and a survivor on a
line in your changed file is a real gap in that file's tests that you are now
the person best placed to close. Either write the assertion that kills it, or
record the mutant in `scripts/mutation/equivalent-mutants.txt` with a proof that
record the mutant in `scripts/mutation/equivalent-mutants/` with a proof that
no input can distinguish it. "It was already there" is not a resolution; leaving
it just guarantees the next person trips over the same survivor. This is the
[Good citizen](#preferences) rule applied to mutation testing.
Expand Down
39 changes: 1 addition & 38 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -1225,43 +1225,6 @@ proves they create the attendee and ledger rows exactly once.

---

## Stale equivalent-mutant line numbers across recent refactors

*Origin: running `deno task mutation:audit-equivalents` while hardening
`src/shared/square.ts` for the confirmed-Square-refunds job.*

`scripts/mutation/equivalent-mutants.txt` carries several entries whose
`file:line:col` no longer points at a generated mutant, so
`deno task mutation:audit-equivalents` aborts with "No generated mutant matches".
The mutants are still real and equivalent; only the code moved. Confirmed stale
entries span at least:
- `src/shared/uptime-kuma/socket.ts` (lines 107, 130, 167, 187 — `===`/`!==`↔loose)
- `src/shared/uptime-kuma/monitors.ts` (lines 87, 101, 116, 119, 138, 193, 227,
250, 306, 325 — `===`/`!==`↔loose and `1000→1001`)
- `src/shared/scheduled-access.ts:16:20 ??→||`
- `src/shared/storage.ts:376:18 application/octet-stream→""`
- `src/ui/templates/admin/images.tsx:84:28` and `:85:20 ??→||`
- `src/features/app/routes.ts:235:14 →"mutated"`

These most likely drifted when the Uptime Kuma modules were split into
one-concept-per-file (#1906) and other recent move/refactor PRs. The audit is a
standalone task (`mutation:audit-equivalents`, not part of `deno task
precommit`), so CI doesn't gate on it; it surfaced here only because the
Square-refunds job ran exhaustive mutation and used the audit to validate its
own equivalent entries. The `square.ts` entries in the equivalent-mutants
registry have been refreshed in place (they drift as the source shifts lines —
the audit command's own output lists every stale `file:line:col`);

Fix: for each stale entry, re-run `deno task mutation <file> '<tests>'
--exhaustive`, locate the surviving equivalent mutant's current `file:line:col`
from the report, and update the line/col in `equivalent-mutants.txt` (or remove
the entry if the static gates now kill it — `mutation:audit-equivalents --write`
does this automatically for entries the lint/type-check gates catch). Then
re-run the audit until it reports no stale entries. Starting point: the audit's
own output lists every stale `file:line:col`.

---

## Square PENDING refunds — propagate a pending result, not a plain false

*Origin: Codex review of PR #1911 (confirmed Square refund outcomes), thread
Expand Down Expand Up @@ -1334,7 +1297,7 @@ a malformed response throws rather than being silently cast. Starting point:
Direct tests at `test/features/api/folded-booking.test.ts` and
`test/features/api/folded-booking/parent-booking.test.ts` kill every non-equivalent mutant on the unchanged `folded-booking.ts`.
Five equivalents (lines 87, 118, 176, 301, 381) are recorded in
`scripts/mutation/equivalent-mutants.txt` with proofs — no unsuppressed survivors remain.
`scripts/mutation/equivalent-mutants/` with proofs — no unsuppressed survivors remain.

## Split `render-selector.test.ts` by what each case actually checks

Expand Down
1 change: 1 addition & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
"@std/assert": "jsr:@std/assert@^1.0.19",
"@std/cli": "jsr:@std/cli@^1.0.30",
"@std/collections": "jsr:@std/collections@^1.2.0",
"@std/csv": "jsr:@std/csv@^1.0.6",
"@std/expect": "jsr:@std/expect@^1.0.18",
"@std/testing": "jsr:@std/testing@^1.0.17",
"@std/path": "jsr:@std/path@^1.1.4"
Expand Down
19 changes: 16 additions & 3 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion e2e-payments/src/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const runSetup = async (
await session.fill("admin_password_confirm", config.adminPassword);
await session.select("country", country);
await session.check("accept_agreement");
await session.clickButton("Complete Setup");
await session.clickButton("Complete setup");
log(` setup complete (admin=${config.adminUsername}, country=${country})`);
};

Expand Down
73 changes: 51 additions & 22 deletions scripts/audit-equivalent-mutants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
#!/usr/bin/env -S deno run --allow-all

import { fromFileUrl, relative, resolve } from "@std/path";
import { splitFlagValues } from "#scripts/flag-values.ts";
import { auditEquivalentMutants } from "#scripts/mutation/equivalent-audit.ts";
import {
EQUIVALENT_MUTANTS_FILE,
EQUIVALENT_MUTANTS_PATH,
} from "#scripts/mutation/ignore.ts";
import { listRegistryFiles } from "#scripts/mutation/ignore.ts";
import { runInSnapshot } from "#scripts/mutation/isolation.ts";
import {
isSnapshotChild,
Expand All @@ -21,28 +20,42 @@ const usage = `Usage: deno task mutation:audit-equivalents [--write]
Runs lint and type-check against every recorded equivalent mutant without
running tests. Pass --write to remove entries that static checks now kill.`;

const parseOptions = (args: string[]): { write: boolean } => {
if (args.length === 0) return { write: false };
if (args.length === 1 && args[0] === "--write") {
return { write: true };
}
if (args.length === 1 && ["-h", "--help"].includes(args[0]!)) {
/** The audit's options. The registry list is internal plumbing: the parent
* enumerates the shards once and hands the same list to its snapshot child,
* so the audited files and the copied-back files can never differ. */
interface AuditOptions {
registry: string[];
write: boolean;
}

const parseOptions = (args: string[]): AuditOptions => {
const { rest, values } = splitFlagValues(args, "--registry");
const registry = values.map((path) => {
if (!path || path.startsWith("--")) {
throw new Error(`--registry needs a path\n\n${usage}`);
}
return path;
});
if (rest.length === 1 && ["-h", "--help"].includes(rest[0]!)) {
console.log(usage);
Deno.exit(0);
}
throw new Error(`Unknown arguments: ${args.join(" ")}\n\n${usage}`);
if (rest.length > 1 || (rest.length === 1 && rest[0] !== "--write")) {
throw new Error(`Unknown arguments: ${rest.join(" ")}\n\n${usage}`);
}
return { registry, write: rest[0] === "--write" };
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

/** Audit inside this checkout, which the snapshot child owns outright. */
const runAudit = async (options: { write: boolean }): Promise<number> => {
const runAudit = async (options: AuditOptions): Promise<number> => {
const controller = new AbortController();
const onSignal = (): void => controller.abort();
onTerminationSignals(onSignal);
const result = await auditEquivalentMutants({
...options,
ignoreFile: EQUIVALENT_MUTANTS_FILE,
ignoreFiles: options.registry.map((path) => resolve(projectRoot, path)),
root: projectRoot,
signal: controller.signal,
write: options.write,
})
.catch((error) => {
if (controller.signal.aborted) Deno.exit(130);
Expand All @@ -60,17 +73,33 @@ const runAudit = async (options: { write: boolean }): Promise<number> => {
return !options.write && result.retained !== result.checked ? 1 : 0;
};

/** Project-relative registry paths, listed before snapshotting so a --write
* audit can carry each pruned file back to the live checkout. */
const registryCopyBackPaths = async (): Promise<string[]> =>
(await listRegistryFiles()).map((file) =>
relative(projectRoot, typeof file === "string" ? file : fromFileUrl(file)),
);

if (import.meta.main) {
// Parsed here too, so a bad flag or --help answers without copying anything.
const options = parseOptions(Deno.args);
if (isSnapshotChild()) {
Deno.exit(await runSnapshotChild(() => runAudit(options)));
}
// The manifest is the parent's to build: a hand-passed file would be
// audited in the snapshot yet never copied back, so refuse it outright.
if (options.registry.length > 0) {
throw new Error(`--registry is internal to the audit\n\n${usage}`);
}
// One shard list serves the audit and the copy-back alike, so a shard added
// while the snapshot is being made can never be pruned and then lost.
const registry = await registryCopyBackPaths();
Deno.exit(
isSnapshotChild()
? await runSnapshotChild(() => runAudit(options))
: await runInSnapshot({
args: Deno.args,
// Only a --write audit rewrites the list, so only it keeps a file.
copyBack: options.write ? [EQUIVALENT_MUTANTS_PATH] : [],
entryScript: "scripts/audit-equivalent-mutants.ts",
}),
await runInSnapshot({
args: [...Deno.args, ...registry.flatMap((path) => ["--registry", path])],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject parent-supplied registry manifests

When the task is invoked with an extra --registry custom.txt --write, parseOptions accepts the undocumented internal flag and this line forwards it to the snapshot child, so that file is audited and pruned; however, copyBack contains only the separately enumerated standard registry files. The command can therefore report success while silently discarding the custom file's pruning. Reject --registry outside isSnapshotChild(), or build the child arguments solely from the parent-enumerated manifest.

AGENTS.md reference: AGENTS.md:L980-L984

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 730c254. The parent now refuses a command-line --registry outright (--registry is internal to the audit, with usage), so the flag only ever reaches the snapshot child carrying the parent-enumerated manifest — the audited set and the copy-back set can no longer diverge.


Generated by Claude Code

// Only a --write audit rewrites the registry, so only it keeps files.
copyBack: options.write ? registry : [],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not copy shards back after a failed audit

When --write prunes one registry shard and then fails while writing a later shard, the child exits nonzero with a partial snapshot, but runInSnapshot still copies every configured path back before combining the copy result with the child exit code (scripts/mutation/isolation.ts:156-163). Consequently this line can publish partial or truncated registry changes to the live checkout even though the audit reports failure; only copy the manifest back after a successful child exit, or make the child's multi-shard pruning atomic.

AGENTS.md reference: AGENTS.md:L98-L98

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4fdf504, taking the second option you offered: the child's shard prune inside the snapshot now writes through the same whole-or-nothing helper, so no truncated shard can ever exist in the work copy for the copy-back to publish. What a crashed --write audit can leave is only a mix of pruned and not-yet-pruned shards — and since every registry record stands alone, that mix is a valid registry (the unpruned entries are simply still listed), which the next audit finishes. Copy-back-on-failure itself is deliberate, pinned by the "keeps the edit even when the run itself reports a failure" test in test/scripts/mutation/isolation/copy-back.test.ts, so gating on the child's exit code would change designed behaviour; removing the truncation hazard closes the actual corruption path.


Generated by Claude Code

entryScript: "scripts/audit-equivalent-mutants.ts",
}),
);
}
21 changes: 21 additions & 0 deletions scripts/flag-values.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Splitting one repeated flag and its values out of a command line.
*/

/** Split argv into the given flag's values and everything else. A flag at the
* end with nothing after it yields undefined, for the caller to refuse with
* its own words. */
export const splitFlagValues = (
args: string[],
flag: string,
): { rest: string[]; values: (string | undefined)[] } => {
const rest: string[] = [];
const values: (string | undefined)[] = [];
const remaining = [...args];
while (remaining.length > 0) {
const value = remaining.shift()!;
if (value === flag) values.push(remaining.shift());
else rest.push(value);
}
return { rest, values };
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Loading