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
9 changes: 7 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,13 @@ jobs:
- name: Typecheck
run: pnpm typecheck

- name: Test
run: pnpm test
- name: Test with coverage gate
# vitest.config.ts enforces 100% statements/branches/functions/lines: the run exits non-zero below any threshold, so this step IS the coverage gate rather than a report-only pass.
run: pnpm test:coverage

- name: Mutation gate
# stryker.config.ts enforces a 100% mutation score (thresholds.break): any surviving mutant in src/domain or src/schemas fails the build.
run: pnpm test:mutation

- name: Build
run: pnpm build
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ peer.on("message", (m) => console.log(`${m.fromName ?? m.from}: ${m.body}`));
peer.on("receipt", (r) => console.log(`status: ${r.status}`));

const sessions = await peer.roster();
const { msgId } = await peer.send(sessions[0]!.pid, "hello from my app");

await peer.subscribeIdle(sessions[0]!.pid);
const first = sessions.find((s) => s.name === "claude");
if (first !== undefined) {
const { msgId } = await peer.send({ pid: first.pid }, "hello from my app");
await peer.subscribeIdle({ pid: first.pid });
}
peer.on("idle", (n) => console.log(`session ${n.state}`));

// …later
Expand Down
31 changes: 31 additions & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
import { exadevConfig } from "@exadev/eslint-config";
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
import globals from "globals";
import type { Rule } from "eslint";

/**
* Bans mutation-exemption comments in any form (the "Stryker" + "disable" pair, case-insensitive). The mutation score is gated at 100% (stryker.config.ts), so exempting a mutant anywhere is a silent hole in that gate: the ban keeps the score honest by forcing an unkilled mutant to be either killed by a test or fixed in the design, never waived by a comment.
*/
const noStrykerDisable: Rule.RuleModule = {
create(context) {
const comments = context.sourceCode.getAllComments();
for (const comment of comments) {
const loc = comment.loc;
if (
loc !== undefined &&
loc !== null &&
/stryker\s+disable/i.test(comment.value)
) {
context.report({
loc,
message:
"Mutation-exemption comments are banned: kill the mutant with a test or fix the design, never exempt it.",
});
}
}
return {};
},
};

export default exadevConfig(
{},
Expand Down Expand Up @@ -32,6 +57,12 @@ export default exadevConfig(
],
},
},
{
plugins: {
local: { rules: { "no-stryker-disable": noStrykerDisable } },
},
rules: { "local/no-stryker-disable": "error" },
},
/* Test fixtures legitimately encode raw protocol values (16-byte tokens,
24-hex hop ids, chain lengths); naming them would obscure the fixture. */
{
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@
"test": "vitest run",
"test:watch": "vitest",
"test:mutation": "stryker run",
"prepublishOnly": "pnpm lint && pnpm typecheck && pnpm test && pnpm build && publint && attw --pack",
"prepare": "husky"
"prepublishOnly": "pnpm lint && pnpm typecheck && pnpm test:coverage && pnpm build && publint && attw --pack",
"prepare": "husky",
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"zod": "4.5.4"
Expand Down Expand Up @@ -90,6 +91,7 @@
"@stryker-mutator/core": "10.0.0",
"@stryker-mutator/vitest-runner": "10.0.0",
"@types/node": "26.4.1",
"@vitest/coverage-v8": "5.0.0",
"commitlint": "21.2.2",
"eslint": "10.10.0",
"eslint-config-prettier": "10.1.8",
Expand Down
103 changes: 98 additions & 5 deletions pnpm-lock.yaml

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

7 changes: 7 additions & 0 deletions stryker.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ const options = {
coverageAnalysis: "perTest",
mutate: ["src/domain/**/*.ts", "src/schemas/**/*.ts", "!src/**/*.test.ts"],
tempDirName: ".stryker-tmp",
// 100% across the board: a surviving mutant is a behaviour the test suite does not pin. Mutation-exemption comments are banned by the lint rule in eslint.config.ts: a mutant that genuinely cannot be killed is a design smell to fix, not to exempt.
thresholds: {
high: 100,
low: 100,
break: 100,
},
ignorePatterns: ["dist", "dist-sea", "coverage", ".turbo"],
};

export default options;
Loading