test(tests): antagonistic tier — fast-check property tests + Stryker mutation testing (PR-gated) (#461)
* feat(#454): add antagonistic tier — fast-check property tests + Stryker mutation config Adds property-based testing (fast-check v4) and mutation testing scaffolding (Stryker v8) as the antagonistic validation tier for lib/*.cjs pure logic. ## Files added ### Shared setup - tests/helpers/fast-check-setup.cjs — configureGlobal({ numRuns:200, seed:42 }) for deterministic CI; override locally with GSD_FC_SEED ### Property test suites (node:test + fast-check) - tests/context-utilization.property.test.cjs — boundary at 60%/70% thresholds (exact Math.ceil boundary, not Math.floor), TypeError on all invalid inputs, overflow clamping to 100%/critical, shape invariants (7 tests, all pass) - tests/prompt-budget.property.test.cjs — estimateTokens monotonicity + ceil(len/4) exactness; applyBudget shape invariant, instructions/roadmap verbatim, budget envelope, omit tracking (11 tests, all pass) - tests/frontmatter.property.test.cjs — extractFrontmatter/reconstructFrontmatter/ spliceFrontmatter never-throw + type shape + splice→extract round-trip (9 tests) - tests/adr-parser.property.test.cjs — shouldRejectAdrStatus boundary (3 statuses only), parseAdrMarkdown shape + title trim invariant (discovered: parser trims trailing whitespace) (9 tests, all pass) - tests/config-schema.property.test.cjs — isValidConfigKey never throws, returns boolean, accepts all VALID/RUNTIME_STATE_KEYS, rejects empty/null/unknown (8 tests) ### Stryker mutation config - stryker.config.mjs — testRunner:'command', mutate bin/lib/**/*.cjs minus 13 generated files, coverageAnalysis:'off', thresholds {high:80,low:60,break:50}, incremental:true, reporters html+clear-text+progress ### CI workflow - .github/workflows/mutation.yml — PR-gating job (pull_request + workflow_dispatch), runs stryker --incremental --since origin/next (changed files only), uploads HTML artifact; SINCE_REF via env not interpolation (injection-safe) ### Package config - package.json: +test:mutation, +test:mutation:since scripts - .gitignore: +.stryker-tmp/, +.stryker-incremental.json, +reports/mutation/ - package-lock.json: fast-check@4.8.0, @stryker-mutator/core@9.6.1 ## Verified node --test on all 5 property test files: 44 tests, 0 failures. Stryker NOT run (slow; reserved for CI). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#454): pin upload-artifact to v7.0.1 SHA used across repo (bad SHA ea165f8d) The SHA ea165f8d65b6e75b540449d3ec4f5dde0c5a4e1 (labeled v4.6.2) does not resolve on GitHub Actions. All other workflows in this repo pin 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a (v7.0.1) — align mutation.yml. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#454): mutation workflow — replace invalid --since flag with changed-core-files --mutate scoping Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: CI Rebase Check <ci@gsd-redux> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
92
stryker.config.mjs
Normal file
92
stryker.config.mjs
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* stryker.config.mjs
|
||||
*
|
||||
* Mutation testing configuration for get-shit-done-redux.
|
||||
*
|
||||
* Test runner: 'command' (built into @stryker-mutator/core)
|
||||
* Runs: node --test over the lib test files via the repo's run-tests invocation.
|
||||
*
|
||||
* Mutate scope: bin/lib/**\/*.cjs, excluding generated files and test files.
|
||||
*
|
||||
* coverageAnalysis: 'off' — command runner does not support per-mutant coverage
|
||||
* thresholds: high=80, low=60, break=50
|
||||
* incremental: true — caches results; PR-scoped runs pass --mutate <changed-files>
|
||||
*
|
||||
* Reports:
|
||||
* - html: reports/mutation/mutation.html
|
||||
* - clear-text (console)
|
||||
* - progress (spinner)
|
||||
*
|
||||
* NOTE: This is incremental / changed-files-only in CI (--mutate <changed-files>)
|
||||
* to stay bounded. Full runs are for local exploration only.
|
||||
*/
|
||||
|
||||
// Generated files that must NEVER be mutated
|
||||
const GENERATED_FILES = [
|
||||
'!get-shit-done/bin/lib/configuration.cjs', // GENERATED — sdk/src/config/index.ts
|
||||
'!get-shit-done/bin/lib/command-aliases.cjs', // GENERATED
|
||||
'!get-shit-done/bin/lib/commands.cjs', // GENERATED
|
||||
'!get-shit-done/bin/lib/core.cjs', // GENERATED
|
||||
'!get-shit-done/bin/lib/install-profiles.cjs', // GENERATED
|
||||
'!get-shit-done/bin/lib/installer-migrations.cjs', // GENERATED
|
||||
'!get-shit-done/bin/lib/phase.cjs', // GENERATED
|
||||
'!get-shit-done/bin/lib/profile-output.cjs', // GENERATED
|
||||
'!get-shit-done/bin/lib/state.cjs', // GENERATED
|
||||
'!get-shit-done/bin/lib/verify.cjs', // GENERATED
|
||||
'!get-shit-done/bin/lib/init.cjs', // GENERATED
|
||||
'!get-shit-done/bin/lib/audit.cjs', // GENERATED
|
||||
'!get-shit-done/bin/lib/gsd2-import.cjs', // GENERATED
|
||||
];
|
||||
|
||||
/** @type {import('@stryker-mutator/core').PartialStrykerOptions} */
|
||||
export default {
|
||||
// ── Test runner ──────────────────────────────────────────────────────────────
|
||||
testRunner: 'command',
|
||||
commandRunner: {
|
||||
// Run property tests + unit tests over lib only.
|
||||
// Deliberately avoids running the full integration suite (slow).
|
||||
command: 'node --test tests/context-utilization.property.test.cjs tests/prompt-budget.property.test.cjs tests/frontmatter.property.test.cjs tests/adr-parser.property.test.cjs tests/config-schema.property.test.cjs tests/adr-parser.test.cjs tests/active-workstream-store.test.cjs',
|
||||
},
|
||||
|
||||
// ── Files to mutate ──────────────────────────────────────────────────────────
|
||||
mutate: [
|
||||
'get-shit-done/bin/lib/**/*.cjs',
|
||||
'!get-shit-done/bin/lib/**/*.test.cjs',
|
||||
...GENERATED_FILES,
|
||||
],
|
||||
|
||||
// ── Coverage ─────────────────────────────────────────────────────────────────
|
||||
// 'off' is required for the command test runner — it cannot instrument per-mutant.
|
||||
coverageAnalysis: 'off',
|
||||
|
||||
// ── Thresholds ───────────────────────────────────────────────────────────────
|
||||
thresholds: {
|
||||
high: 80,
|
||||
low: 60,
|
||||
break: 50,
|
||||
},
|
||||
|
||||
// ── Incremental mode ─────────────────────────────────────────────────────────
|
||||
// Cache mutation results; re-run only changed mutants on subsequent calls.
|
||||
// In CI the workflow computes changed files and passes: stryker run --incremental --mutate <list>
|
||||
incremental: true,
|
||||
incrementalFile: '.stryker-incremental.json',
|
||||
|
||||
// ── Reporters ────────────────────────────────────────────────────────────────
|
||||
reporters: ['html', 'clear-text', 'progress'],
|
||||
htmlReporter: {
|
||||
fileName: 'reports/mutation/mutation.html',
|
||||
},
|
||||
|
||||
// ── Temp directory ───────────────────────────────────────────────────────────
|
||||
tempDirName: '.stryker-tmp',
|
||||
|
||||
// ── Ignore patterns ──────────────────────────────────────────────────────────
|
||||
ignorePatterns: [
|
||||
'node_modules',
|
||||
'reports',
|
||||
'.stryker-tmp',
|
||||
'coverage',
|
||||
'hooks/dist',
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user