Skip to content

Instantly share code, notes, and snippets.

@LeoVS09
Created March 6, 2026 21:33
Show Gist options
  • Select an option

  • Save LeoVS09/fef30aa27d40582dc9bf79f65e8a589d to your computer and use it in GitHub Desktop.

Select an option

Save LeoVS09/fef30aa27d40582dc9bf79f65e8a589d to your computer and use it in GitHub Desktop.
Agent Harness for node-typescript projects

Development Commands

# Install dependencies
npm install

# Development with watch mode
npm run dev

# Build the project
npm run build

# Start production build
npm run start:prod

# Linting (runs jscpd + knip + eslint)
npm run lint              # Run all linters
npm run lint:fix          # Run all linters with auto-fix (jscpd + knip --fix + eslint --fix)
npm run lint:eslint       # ESLint only
npm run lint:knip         # Knip (unused exports/dependencies)
npm run lint:jspd         # jscpd (copy-paste detection)

# Testing
npm run test              # Run all tests (coverage + mutation + e2e)
npm run test:only         # Run unit + e2e tests (skip mutation)
npm run test:unit         # Unit tests only
npm run test:cov          # Unit tests with coverage
npm run test:mutation     # Mutation testing (Stryker)
npm run test:e2e          # End-to-end tests

Important: insted of npx tsx use npm run build

Development Workflow

After making code changes, always:

  1. Run linting: npm run lint to lint changed files and ensure code style compliance
  2. Run tests: npm run test to verify all tests pass

When writing tests for new or modified code, create a corresponding <module>.spec.ts file alongside the edited/created module.

Testing

  • Framework: Jest with ts-jest
  • Unit Tests: Located in src/ with .spec.ts suffix
  • E2E Tests: Located in test/ directory with dedicated jest-e2e.json config
  • Coverage: Generated in coverage/ directory
import antfu from '@antfu/eslint-config'
import validateFilename from 'eslint-plugin-validate-filename'
import sonarjs from 'eslint-plugin-sonarjs'
import tseslint from 'typescript-eslint'
import promisePlugin from 'eslint-plugin-promise'
import stepDownPlugin from './eslint.plugin.step-down-rule.mjs'
import aliasPlugin from './eslint.plugin.root-alias.mjs'
import noNeverReturnPlugin from './eslint.plugin.no-never-return-type.cjs'
export default antfu({
plugins: {
'alias': aliasPlugin,
'validate-filename': validateFilename,
'promise': promisePlugin,
'step-down-rule': stepDownPlugin,
},
ignores: ['**/*.spec.ts', '**/*.test.ts', '**/*.e2e-spec.ts', '**/*.test-utils.ts', '**/*.md', 'test/e2e-env-setup.ts', 'smoke-tests/**'],
rules: {
'alias/prefer-alias': 'error',
// Disallow "util", "common", "helper" in file names (e.g. foo.util.ts, common.ts, bar.helpers.ts)
'validate-filename/naming-rules': [
'error',
{
rules: [
{
target: '**/*.ts',
patterns: '^(?!.*(util|common|helper|function)).+$',
},
],
},
],
'test/prefer-lowercase-title': 'off',
'ts/consistent-type-imports': 'off', // Disable for NestJS - injectable classes need value imports for DI to work
'promise/prefer-await-to-then': 'error',
'perfectionist/sort-named-imports': 'off',
'step-down-rule/step-down': 'error',
// Complexity rules
'complexity': ['error', 10], // cyclicomatic complexity, for congitive complexity check sonarjs/cognitive-complexity at the end of the file
'max-depth': ['error', 2],
'max-lines-per-function': ['error', { max: 40, skipBlankLines: true, skipComments: true }],
'max-statements': ['error', 10],
'max-lines': ['error', { max: 150, skipBlankLines: true, skipComments: true }],
'max-nested-callbacks': ['error', 3],
'max-params': ['error', 3],
},
}).append(
// Add strictTypeChecked rules (skip config[0] which re-registers the plugin/parser already provided by antfu)
...tseslint.configs.strictTypeChecked.slice(1),
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
plugins: { sonarjs, 'no-never-return': noNeverReturnPlugin },
rules: {
// ═══════════════════════════════════════════════════════════════
// CUSTOM — ban never-returning functions
// ═══════════════════════════════════════════════════════════════
// such function usally result of LLM writing code using side effect pattern to validate input or handle error.
'no-never-return/no-never-return-type': 'error',
// ═══════════════════════════════════════════════════════════════
// JSDOC — documentation enforcement
// ═══════════════════════════════════════════════════════════════
'jsdoc/require-jsdoc': [
'error',
{
require: {
FunctionDeclaration: true,
MethodDefinition: true,
ClassDeclaration: true,
ArrowFunctionExpression: false,
FunctionExpression: false,
},
checkConstructors: true,
checkGetters: true,
checkSetters: true,
},
],
'jsdoc/require-description': 'error',
'jsdoc/require-param': 'error',
'jsdoc/require-returns': 'error',
'jsdoc/check-param-names': 'error',
'jsdoc/no-blank-blocks': 'error',
// ═══════════════════════════════════════════════════════════════
// UNICORN — catch block hygiene
// ═══════════════════════════════════════════════════════════════
'unicorn/catch-error-name': ['error', { name: 'error' }],
'unicorn/prefer-optional-catch-binding': 'error',
'unicorn/throw-new-error': 'off', // fail catch decorators
// ═══════════════════════════════════════════════════════════════
// UNICORN — general strictness
// ═══════════════════════════════════════════════════════════════
'unicorn/consistent-destructuring': 'error',
'unicorn/consistent-function-scoping': 'error',
'unicorn/custom-error-definition': 'error',
'unicorn/no-lonely-if': 'error',
'unicorn/no-nested-ternary': 'error',
'unicorn/no-static-only-class': 'error',
'unicorn/prefer-class-fields': 'error',
// ═══════════════════════════════════════════════════════════════
// TYPESCRIPT-ESLINT — throw/catch safety
// ═══════════════════════════════════════════════════════════════
'@typescript-eslint/use-unknown-in-catch-callback-variable': 'error',
'@typescript-eslint/only-throw-error': 'error',
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
// ═══════════════════════════════════════════════════════════════
// TYPESCRIPT-ESLINT — class method purity
// ═══════════════════════════════════════════════════════════════
'class-methods-use-this': 'off',
'@typescript-eslint/class-methods-use-this': ['error', {
ignoreOverrideMethods: true,
ignoreClassesThatImplementAnInterface: 'public-fields',
}],
// ═══════════════════════════════════════════════════════════════
// SONARJS — naming conventions (ban vague names)
// ═══════════════════════════════════════════════════════════════
// Class names: PascalCase, must NOT contain util, common, helper, function (any case)
'sonarjs/class-name': [
'error',
{
format:
'^(?!.*(util|Util|UTIL|common|Common|COMMON|helper|Helper|HELPER|function|Function|FUNCTION))[A-Z][a-zA-Z0-9]*$',
},
],
// Function names: camelCase or PascalCase, must NOT contain util, common, helper, function (any case)
// PascalCase allowed for decorators
'sonarjs/function-name': [
'error',
{
format:
'^(?!.*(util|Util|UTIL|common|Common|COMMON|helper|Helper|HELPER|function|Function|FUNCTION))[a-zA-Z][a-zA-Z0-9]*$',
},
],
// Variable names: camelCase, PascalCase or UPPER_SNAKE_CASE, must NOT contain util, common, helper, function (any case)
'sonarjs/variable-name': [
'error',
{
format:
'^(?!.*(util|Util|UTIL|common|Common|COMMON|helper|Helper|HELPER|function|Function|FUNCTION))([a-z][a-zA-Z0-9]*|[A-Z][A-Z0-9_]*|[A-Z][a-zA-Z0-9]*|_{1,5})$',
},
],
// ═══════════════════════════════════════════════════════════════
// SONARJS — complexity & control flow
// ═══════════════════════════════════════════════════════════════
// Designed for modern devs with minimal attention span
// Code with cognitive complexity 5 or upper hard to read in 5 seconds
'sonarjs/cognitive-complexity': ['error', 4],
'sonarjs/nested-control-flow': ['error', { maximumNestingLevel: 2 }],
'sonarjs/too-many-break-or-continue-in-loop': 'error',
'sonarjs/elseif-without-else': 'error',
'sonarjs/no-nested-conditional': 'error',
'sonarjs/no-same-line-conditional': 'error',
'sonarjs/conditional-indentation': 'error',
// ═══════════════════════════════════════════════════════════════
// SONARJS — dead code & redundancy
// ═══════════════════════════════════════════════════════════════
'sonarjs/no-all-duplicated-branches': 'error',
'sonarjs/no-duplicated-branches': 'error',
'sonarjs/no-dead-store': 'error',
'sonarjs/no-redundant-assignments': 'error',
'sonarjs/no-identical-functions': ['error', 3],
'sonarjs/no-useless-catch': 'error',
'sonarjs/no-useless-increment': 'error',
'sonarjs/useless-string-operation': 'error',
'sonarjs/prefer-immediate-return': 'error',
// ═══════════════════════════════════════════════════════════════
// SONARJS — nesting & assignments
// ═══════════════════════════════════════════════════════════════
'sonarjs/no-nested-assignment': 'error',
'sonarjs/no-nested-functions': 'error',
'sonarjs/no-nested-incdec': 'error',
'sonarjs/no-parameter-reassignment': 'error',
'sonarjs/destructuring-assignment-syntax': 'error',
// ═══════════════════════════════════════════════════════════════
// SONARJS — loops
// ═══════════════════════════════════════════════════════════════
'sonarjs/misplaced-loop-counter': 'error',
'sonarjs/updated-loop-counter': 'error',
// ═══════════════════════════════════════════════════════════════
// SONARJS — functions & declarations
// ═══════════════════════════════════════════════════════════════
'sonarjs/no-function-declaration-in-block': 'error',
'sonarjs/no-globals-shadowing': 'error',
'sonarjs/no-fallthrough': 'error',
'sonarjs/no-reference-error': 'error',
'sonarjs/no-unthrown-error': 'error',
'sonarjs/prefer-type-guard': 'error',
// ═══════════════════════════════════════════════════════════════
// SONARJS — promises & async
// ═══════════════════════════════════════════════════════════════
'sonarjs/no-try-promise': 'error',
// ═══════════════════════════════════════════════════════════════
// SONARJS — security
// ═══════════════════════════════════════════════════════════════
'sonarjs/no-hardcoded-ip': 'error',
'sonarjs/no-hardcoded-passwords': 'error',
'sonarjs/no-hardcoded-secrets': 'error',
'sonarjs/os-command': 'error',
// ═══════════════════════════════════════════════════════════════
// SONARJS — testing
// ═══════════════════════════════════════════════════════════════
'sonarjs/no-skipped-tests': 'error',
'sonarjs/stable-tests': 'error',
// ═══════════════════════════════════════════════════════════════
// ESLINT CORE
// ═══════════════════════════════════════════════════════════════
'no-warning-comments': [
'error',
{
terms: ['jscpd:ignore-start', 'jscpd:ignore-end'],
location: 'anywhere',
},
],
'prefer-const': 'error',
'init-declarations': ['error', 'always'],
'id-length': [
'error',
{
min: 3,
max: 25,
exceptions: ['i', 'j', 'k', 'x', 'y', 'z', '_', 'id', 'on', 'in', 'of'],
},
],
'padding-line-between-statements': [
'error',
// Blank line after variable declarations
{ blankLine: 'always', prev: ['const', 'let'], next: '*' },
// ...except between consecutive declarations
{ blankLine: 'any', prev: ['const', 'let'], next: ['const', 'let'] },
// Blank line before return
{ blankLine: 'always', prev: '*', next: 'return' },
// Blank line before/after control flow
{ blankLine: 'always', prev: '*', next: ['if', 'for', 'while', 'switch', 'try'] },
// ...except when const/let is directly before if
{ blankLine: 'any', prev: ['const', 'let'], next: 'if' },
{ blankLine: 'always', prev: ['if', 'for', 'while', 'switch', 'try'], next: '*' },
],
'preserve-caught-error': 'error',
// ═══════════════════════════════════════════════════════════════
// CATCH DELEGATION DETECTION (no-restricted-syntax)
// ═══════════════════════════════════════════════════════════════
'no-restricted-syntax': [
'error',
// 1. Single-statement catch → return fn(...)
{
selector:
'CatchClause > BlockStatement[body.length=1] > ReturnStatement > CallExpression',
message:
'Do not delegate error handling to another function via return. Handle the error inline or re-throw.',
},
// 2. Single-statement catch → fn(...)
{
selector:
'CatchClause > BlockStatement[body.length=1] > ExpressionStatement > CallExpression',
message:
'Do not delegate error handling to another function. Handle the error inline or re-throw.',
},
// 3a. Passing `error` directly to any function in catch except this.logger.* or console.*
{
selector:
"CatchClause[param.name='error'] CallExpression:not([callee.object.property.name='logger']):not([callee.object.name='console']) > Identifier.arguments[name='error']",
message:
'Do not pass the caught error to functions other than this.logger.*. Handle it at the catch site!',
},
// 3b. Passing `error` wrapped in an object — bypasses 3a via { error } or { key: error }
{
selector:
"CatchClause[param.name='error'] CallExpression:not([callee.object.property.name='logger']):not([callee.object.name='console']) Property > Identifier.value[name='error']",
message:
'Do not wrap the caught error in an object to pass to non-logger functions. Handle it at the catch site!',
},
// 3c. Passing `error` with a type assertion — bypasses 3a via `error as T`
{
selector:
"CatchClause[param.name='error'] CallExpression:not([callee.object.property.name='logger']):not([callee.object.name='console']) > TSAsExpression.arguments > Identifier[name='error']",
message:
'Do not use type assertions on the caught error to pass to non-logger functions. Handle it at the catch site!',
},
// 4. Ban inline object type literals in function return types
// Forces extracting to named types/interfaces
{
selector:
':function > TSTypeAnnotation TSTypeLiteral',
message:
'Do not use inline object types in return types. Extract to a named type or interface.',
},
// 5. Catch parameter must be named exactly `error` — no descriptive variants
{
selector:
"CatchClause > Identifier[name!='error']",
message:
"Catch parameter must be named exactly 'error'. Do not use descriptive names like 'caughtError'.",
},
// 6. No aliasing the caught error — prevents bypassing catch-safety rules
{
selector:
"CatchClause VariableDeclarator[init.name='error']",
message:
"Do not alias the caught error. Use 'error' directly — renaming bypasses catch-safety linting rules.",
},
// 6b. No reassigning the caught error to an outer variable — prevents bypassing catch-safety rules
{
selector:
"CatchClause[param.name='error'] AssignmentExpression[right.name='error']",
message:
"Do not reassign the caught error to another variable. Handle it directly in the catch block.",
},
// 7. Ban throw fn() — force throw new Error() or subclass directly
{
selector:
'ThrowStatement > CallExpression',
message:
'Do not throw the result of a function call. Throw a new Error (or subclass) directly.',
},
// 8. Ban return new Error/Exception — errors must be thrown, not returned
{
selector:
'ReturnStatement > NewExpression[callee.name=/(?:Error|Exception)$/]',
message:
'Do not return Error/Exception objects. Throw them directly instead.',
},
// 9. Ban assigning new Error/Exception to a variable — throw immediately
{
selector:
'VariableDeclarator > NewExpression[callee.name=/(?:Error|Exception)$/]',
message:
'Do not assign Error/Exception objects to variables. Throw them directly instead.',
},
// 10. Ban .catch() — use try/catch with await instead
{
selector: 'CallExpression[callee.property.name=\'catch\']',
message:
'Do not use .catch(). Use try/catch with await instead.',
},
// 11. Ban static methods — use instance methods instead
{
selector: 'MethodDefinition[static=true]',
message:
'Do not use static methods. Convert to an instance method or extract to a standalone function.',
},
// 12. Ban static properties — use instance properties instead
{
selector: 'PropertyDefinition[static=true]',
message:
'Do not use static properties. Use instance properties instead.',
},
],
},
},
)
/**
* ESLint plugin: no-never-return-type (ESLint 9 flat config compatible)
*
* Bans functions whose return type is `never` — functions that only throw
* exceptions (used for wrapping or re-throwing errors). These should be
* restructured to throw at the call site instead.
*
* Uses @typescript-eslint type-aware linting to inspect the TypeScript
* type checker's resolved return type for each function.
*/
const { ESLintUtils } = require('@typescript-eslint/utils')
const ts = require('typescript')
const createRule = ESLintUtils.RuleCreator(
(name) => `https://github.com/NeoLabHQ/decision-engine/blob/master/docs/eslint-rules/${name}`,
)
const noNeverReturnType = createRule({
name: 'no-never-return-type',
meta: {
type: 'problem',
docs: { description: 'Disallow functions that return `never`. Restructure to throw at the call site.' },
messages: {
noNeverReturn:
'Function has return type `never`. Throw directly at the call site instead of wrapping error in a function or throwing as side effect during validation.',
},
schema: [],
},
defaultOptions: [],
create(context) {
const services = ESLintUtils.getParserServices(context)
const checker = services.program.getTypeChecker()
/**
* Returns true if the given function-like node resolves to a `never`
* return type in the TypeScript type checker.
*
* @param functionNode - ESTree function-like node (declaration, expression, or arrow)
* @returns {boolean} whether the function's return type is `never`
*/
function hasNeverReturnType(functionNode) {
const tsNode = services.esTreeNodeToTSNodeMap.get(functionNode)
const signature = checker.getSignatureFromDeclaration(tsNode)
if (!signature) {
return false
}
const returnType = checker.getReturnTypeOfSignature(signature)
return Boolean(returnType.getFlags() & ts.TypeFlags.Never)
}
/**
* Returns true when the function node is used as a callback — either as
* an object property value or as an argument in a function/method call.
* These are intentional throw-only callbacks (e.g. Catch decorator handlers)
* and should not be flagged.
*
* @param node - ESTree function-like node to inspect
* @returns {boolean} whether the function is a callback
*/
function isCallbackFunction(node) {
const { parent } = node
if (!parent) {
return false
}
// Object property value: { handle: () => { throw ... } }
if (parent.type === 'Property' && parent.value === node) {
return true
}
// Direct argument in a function/method call: someFunction(() => { throw ... })
if (parent.type === 'CallExpression' && parent.arguments.includes(node)) {
return true
}
return false
}
/**
* Checks a function-like node and reports if it has `never` return type.
*
* @param node - the node to report on (may differ from the function node for MethodDefinition)
* @param functionNode - the function-like node to type-check (defaults to node)
*/
function checkAndReport(node, functionNode) {
if (hasNeverReturnType(functionNode ?? node)) {
context.report({ node, messageId: 'noNeverReturn' })
}
}
return {
FunctionDeclaration: checkAndReport,
ArrowFunctionExpression(node) {
if (isCallbackFunction(node)) {
return
}
checkAndReport(node)
},
// Standalone function expressions (not class methods — those are handled by MethodDefinition)
FunctionExpression(node) {
if (node.parent.type === 'MethodDefinition') {
return
}
if (isCallbackFunction(node)) {
return
}
checkAndReport(node)
},
// Class methods — type-check the inner FunctionExpression, report on the MethodDefinition
MethodDefinition(node) {
if (node.value) {
checkAndReport(node, node.value)
}
},
}
},
})
module.exports = {
meta: {
name: 'eslint-plugin-no-never-return-type',
version: '1.0.0',
},
rules: {
'no-never-return-type': noNeverReturnType,
},
}
/**
* ESLint plugin: step-down-rule (ESLint 9 flat config compatible)
*
* Enforces top-down call structure — callers appear before callees.
* Based on https://github.com/skabbi/eslint-plugin-the-step-down-rule
*
* ESLint 9 migration: context.getScope() → sourceCode.getScope(node)
* Fix: decorator factory functions defined after usage are valid top-down.
*/
function createRule() {
return {
meta: {
type: 'suggestion',
messages: {
stepDown: 'Expected \'{{callee}}\' to be defined after \'{{caller}}\'. Follow top-to-bottom definitions',
},
},
create(context) {
const sourceCode = context.sourceCode
let requiredFunctions = []
let methodStack = []
return {
Program(node) {
methodStack = []
requiredFunctions = []
findVariablesInScope(sourceCode.getScope(node))
},
MethodDefinition(node) {
if (node.kind === 'method') {
methodStack.push(node.key.name)
}
},
'CallExpression:exit'(node) {
if (node.callee.type !== 'MemberExpression' || node.callee.property.type !== 'Identifier') {
return
}
if (node.callee.object.type !== 'ThisExpression') {
return
}
const calledMethodName = node.callee.property.name
const calledIndex = methodStack.indexOf(calledMethodName)
if (calledIndex === -1 || calledIndex >= methodStack.length - 1) {
return
}
const callerName = methodStack[methodStack.length - 1] ?? '(unknown)'
context.report({
node,
messageId: 'stepDown',
data: { callee: calledMethodName, caller: callerName },
})
},
}
function findVariablesInScope(scope) {
for (const reference of scope.references) {
const variable = reference.resolved
collectRequiredFunction(reference)
if (!isViolation(reference, variable)) {
continue
}
context.report({
node: reference.identifier,
messageId: 'stepDown',
data: {
callee: reference.identifier.name,
caller: resolveCallerName(reference),
},
})
}
for (const childScope of scope.childScopes) {
findVariablesInScope(childScope)
}
}
function collectRequiredFunction(reference) {
if (
reference?.identifier?.name === 'require'
&& reference.identifier.parent?.parent?.id?.name
) {
requiredFunctions.push(reference.identifier.parent.parent.id.name)
}
}
function isViolation(reference, variable) {
if (reference.init || !variable || variable.identifiers.length === 0) {
return false
}
if (isInsideDecorator(reference)) {
return false
}
if (isNotAFunctionCall(reference)) {
return false
}
// Cross-scope call to a function defined below → valid top-down
if (isCallingDown(variable, reference) && !isSameScope(variable, reference)) {
return false
}
// Same-scope call to a function defined above → valid (not top-down violation)
if (isSameScope(variable, reference) && !isCallingDown(variable, reference)) {
return false
}
if (requiredFunctions.includes(reference.identifier.name)) {
return false
}
if (isNotADeclaration(variable)) {
return false
}
if (isRecursiveFunction(reference.identifier, reference.identifier.name)) {
return false
}
if (!isOuterVariable(variable, reference) && !isFunctionDef(variable)) {
return false
}
return true
}
function isInsideDecorator(reference) {
let node = reference.identifier.parent
while (node) {
if (node.type === 'Decorator') {
return true
}
node = node.parent
}
return false
}
function isNotAFunctionCall(reference) {
const { parent } = reference.identifier
return parent.type !== 'CallExpression'
|| reference.identifier.name !== parent.callee?.name
}
function isCallingDown(variable, reference) {
return variable.identifiers[0].range[1] > reference.identifier.range[1]
}
function isSameScope(variable, reference) {
return variable.scope.variableScope === reference.from.variableScope
}
function isNotADeclaration(variable) {
const { type } = variable.identifiers[0].parent
return type !== 'VariableDeclarator' && type !== 'FunctionDeclaration'
}
function isRecursiveFunction(identifier, funcName) {
if (!identifier.parent) {
return false
}
if (identifier.parent.id?.name === funcName) {
return true
}
return isRecursiveFunction(identifier.parent, funcName)
}
function isOuterVariable(variable, reference) {
return variable.defs[0].type === 'Variable'
&& variable.scope.variableScope !== reference.from.variableScope
}
function isFunctionDef(variable) {
return variable.defs[0].type === 'FunctionName'
}
function resolveCallerName(reference) {
let scope = reference.from
while (scope) {
const name = nameFromScope(scope)
if (name) {
return name
}
scope = scope.upper
}
return '(unknown)'
}
function nameFromScope(scope) {
const block = scope.block
// function foo() {} or async function catchWrapper() {}
if (block.id?.name) {
return block.id.name
}
// const foo = () => {} or const foo = function() {}
if (block.parent?.type === 'VariableDeclarator' && block.parent.id?.name) {
return block.parent.id.name
}
// { validate(...args) {} } — object method shorthand
if (block.parent?.type === 'Property' && block.parent.key?.name) {
return block.parent.key.name
}
// class method: fetchScores() {}
if (block.parent?.type === 'MethodDefinition' && block.parent.key?.name) {
return block.parent.key.name
}
return null
}
},
}
}
export default {
meta: {
name: 'eslint-plugin-step-down-rule',
version: '1.0.0',
},
rules: {
'step-down': createRule(),
},
}
const path = require('node:path')
const { pathsToModuleNameMapper } = require('ts-jest')
const tsconfig = require('../tsconfig.json')
module.exports = {
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: '../',
testEnvironment: 'node',
testRegex: '\\.spec\\.ts$',
transform: {
'^.+\\.(t|j)s$': 'ts-jest',
},
// reflect-metadata is required by class-transformer @Type() decorator
setupFiles: ['reflect-metadata'],
modulePaths: [tsconfig.compilerOptions.baseUrl],
moduleNameMapper: pathsToModuleNameMapper(tsconfig.compilerOptions.paths, { prefix: '<rootDir>/' }),
reporters: ['default', '<rootDir>/test/custom-reporter.js'],
testTimeout: 60000,
// Coverage collection
collectCoverageFrom: [
'src/**/*.(t|j)s',
'!src/**/*.spec.ts',
'!src/**/*.module.ts',
'!src/main.ts',
],
coverageDirectory: path.resolve(__dirname, '..', 'coverage'),
coverageReporters: ['text', 'lcov', 'json-summary'],
// Coverage thresholds - automatically maintained by jest-it-up (posttest:cov)
coverageThreshold: {
global: {
branches: 85.67,
functions: 94.39,
lines: 98.76,
statements: 98.77,
},
},
}
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"entry": ["src/main.{js,ts}"],
"project": ["src/**/*.{js,ts}"],
"tags": ["-lintignore"],
"rules": {
"devDependencies": "off",
"exports": "error",
"types": "off"
},
"ignoreBinaries": ["tsx"],
"ignoreDependencies": [
"@as-integrations/express5",
"pino-pretty",
"@stryker-mutator/api"
]
}
{
"name": "example-agent-harness",
"scripts": {
"build": "nest build",
"start": "nest start",
"dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"typecheck": "tsc --noEmit",
"lint:fix": "npm run typecheck && npm run lint:jscpd && npm run lint:eslint -- --fix && npm run lint:knip -- --fix",
"lint": "npm run typecheck && npm run lint:jscpd && npm run lint:knip && npm run lint:eslint",
"lint:eslint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
"lint:jscpd": "jscpd --pattern 'src/**/*.{ts,tsx}' -i '**/*.spec.*' -t 0.1",
"lint:knip": "knip",
"test": "npm run test:cov && npm run test:mutation && npm run test:e2e",
"test:only": "npm run test:unit && npm run test:e2e",
"test:unit": "jest --config ./test/jest.config.js",
"test:cov": "jest --config ./test/jest.config.js --coverage && npm run posttest:cov",
"posttest:cov": "jest-it-up --config ./test/jest.config.js",
"test:mutation": "stryker run",
"test:e2e": "jest --config ./test/jest.e2e.config.js --runInBand"
},
"dependencies": {
"@apollo/server": "^5.4.0",
"@as-integrations/express5": "^1.1.2",
"@gorules/zen-engine": "^0.51.5",
"@itgorillaz/configify": "^4.0.2",
"@nestjs/apollo": "^13.2.4",
"@nestjs/axios": "^4.0.1",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/graphql": "^13.2.4",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/terminus": "^11.1.1",
"axios": "^1.13.2",
"class-transformer": "^0.5.1",
"graphql": "^16.12.0",
"luxon": "^3.7.2",
"nestjs-log-decorator": "^1.4.0",
"nestjs-pino": "^4.5.0",
"passport": "^0.7.0",
"pino-pretty": "^13.1.3",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@antfu/eslint-config": "^7.6.1",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@stryker-mutator/core": "^9.5.1",
"@stryker-mutator/jest-runner": "^9.5.1",
"@stryker-mutator/typescript-checker": "^9.5.1",
"@types/express": "^5.0.0",
"@types/jaro-winkler": "^0.2.4",
"@types/jest": "^30.0.0",
"@types/luxon": "^3.7.1",
"@types/node": "^22.19.11",
"@types/passport": "^1.0.17",
"@types/supertest": "^6.0.2",
"@typescript-eslint/parser": "^8.56.1",
"@typescript-eslint/rule-tester": "^8.56.1",
"@typescript-eslint/utils": "^8.56.1",
"class-validator": "^0.14.3",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-sonarjs": "^4.0.0",
"eslint-plugin-validate-filename": "^1.2.0",
"globals": "^16.0.0",
"jest": "^30.0.0",
"jest-it-up": "^4.0.1",
"jscpd": "^4.0.8",
"knip": "^5.85.0",
"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.56.1"
}
}
// @ts-check
/** @type {import('@stryker-mutator/api/core').PartialStrykerOptions} */
export default {
mutate: [
'src/**/*.ts',
'!src/**/*.spec.ts',
'!src/**/*.module.ts',
'!src/main.ts',
'!src/schema.gql',
],
testRunner: 'jest',
jest: {
projectType: 'custom',
configFile: 'test/jest.config.js',
enableFindRelatedTests: true,
},
checkers: ['typescript'],
tsconfigFile: 'tsconfig.json',
coverageAnalysis: 'perTest',
reporters: ['clear-text', 'progress', 'html', 'json'],
htmlReporter: {
fileName: 'reports/mutation/index.html',
},
jsonReporter: {
fileName: 'reports/mutation/mutation-report.json',
},
// Quality thresholds
thresholds: {
high: 90,
low: 85,
break: 80,
},
ignoreStatic: true,
timeoutMS: 10000,
timeoutFactor: 1.5,
incremental: true,
incrementalFile: 'reports/stryker-incremental.json',
tempDirName: '.stryker-tmp',
cleanTempDir: true,
};
{
"compilerOptions": {
"incremental": true,
"target": "ES2023",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"]
},
"module": "nodenext",
"moduleResolution": "nodenext",
"resolvePackageJsonExports": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitOverride": true,
"declaration": true,
"outDir": "./dist",
"removeComments": true,
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"skipLibCheck": true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment