@boyingliu01/xp-gate 0.19.3 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/gate-4.sh +83 -18
- package/adapters/gate-8.sh +115 -29
- package/adapters/iac.sh +1 -1
- package/adapters/powershell.sh +60 -5
- package/adapters/python.sh +38 -4
- package/adapters/typescript.sh +15 -60
- package/bin/xp-gate.js +15 -16
- package/build-integrity/__tests__/import-resolver.test.ts +59 -0
- package/build-integrity/gate-10.ts +17 -2
- package/gate-10.sh +35 -2
- package/gate-4.sh +110 -21
- package/gate-8.sh +115 -29
- package/hooks/gate-10.sh +35 -2
- package/hooks/gate-4.sh +110 -21
- package/hooks/gate-8.sh +115 -29
- package/hooks/lib/jscpd-run.sh +63 -0
- package/hooks/lib/sprint-gate-report.sh +58 -0
- package/hooks/lib/test-failure.sh +152 -0
- package/hooks/lib/typecheck.sh +141 -0
- package/hooks/lib/validate-code-walkthrough.cjs +43 -5
- package/hooks/post-merge +100 -0
- package/hooks/pre-commit +514 -184
- package/hooks/pre-push +75 -19
- package/lib/__tests__/bootstrap.test.js +23 -41
- package/lib/__tests__/check-version.test.js +8 -1
- package/lib/__tests__/detect-deps.test.js +16 -0
- package/lib/__tests__/doctor-drift-round2.test.js +212 -0
- package/lib/__tests__/doctor-executed-surface.test.js +209 -0
- package/lib/__tests__/doctor-hook-drift.test.js +329 -0
- package/lib/__tests__/doctor-machine-mutation.test.js +116 -0
- package/lib/__tests__/doctor-module-drift.test.js +206 -0
- package/lib/__tests__/doctor-module-resolution-parity.test.js +314 -0
- package/lib/__tests__/doctor-path-equivalence.test.js +59 -0
- package/lib/__tests__/doctor-repair-boundary.test.js +52 -0
- package/lib/__tests__/doctor-tui-skill-freshness.test.js +105 -0
- package/lib/__tests__/doctor.test.js +155 -0
- package/lib/__tests__/gate-runner.test.js +19 -3
- package/lib/__tests__/helpers/lib-sandbox.cjs +55 -0
- package/lib/__tests__/init.test.js +44 -17
- package/lib/__tests__/install-skill-full-dir.test.js +151 -0
- package/lib/__tests__/install-skill.test.js +51 -132
- package/lib/__tests__/language-tools-detect.test.js +158 -0
- package/lib/__tests__/phase-transition.test.js +15 -1
- package/lib/__tests__/qoder-delphi-agents.test.js +285 -0
- package/lib/__tests__/shared-paths.test.js +20 -0
- package/lib/__tests__/shared-utils.test.js +69 -0
- package/lib/__tests__/skill-flags.test.js +48 -0
- package/lib/__tests__/ui-detector.test.ts +6 -2
- package/lib/__tests__/uninstall.test.js +20 -0
- package/lib/__tests__/update-hooks.test.js +12 -0
- package/lib/__tests__/update-skill.test.js +7 -4
- package/lib/detect-deps.js +3 -20
- package/lib/doctor-tui.js +22 -5
- package/lib/doctor.js +1178 -31
- package/lib/init.js +184 -33
- package/lib/install-cmd.js +7 -4
- package/lib/install-skill.js +38 -95
- package/lib/language-tools.js +83 -4
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/shared-paths.js +5 -2
- package/lib/shared-utils.js +47 -3
- package/lib/uninstall.js +12 -0
- package/lib/update-hooks.js +4 -4
- package/lib/update-skill.js +6 -7
- package/mock-policy/AGENTS.md +4 -4
- package/mutation/AGENTS.md +4 -4
- package/mutation/__tests__/gate-m.test.ts +66 -14
- package/mutation/__tests__/mutmut-runner.test.ts +91 -42
- package/mutation/__tests__/test-filter-parity.test.ts +37 -0
- package/mutation/gate-m.ts +21 -8
- package/package.json +2 -2
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/claude-code/skills/delphi-review/SKILL.md +98 -14
- package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/claude-code/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/dsh/README.md +169 -4
- package/plugins/dsh/dsh-overlay.patch.yml +67 -0
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/scripts/prepack.cjs +24 -19
- package/plugins/dsh/setup-dsh.ps1 +160 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/dsh/skills/delphi-review/SKILL.md +98 -14
- package/plugins/dsh/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/dsh/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/dsh/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/dsh/src/command.test.ts +66 -2
- package/plugins/dsh/src/command.ts +36 -14
- package/plugins/dsh/src/contract.test.ts +31 -1
- package/plugins/dsh/src/delphi-config.test.ts +190 -0
- package/plugins/dsh/src/delphi-config.ts +223 -0
- package/plugins/dsh/src/delphi-run.test.ts +181 -0
- package/plugins/dsh/src/delphi-run.ts +129 -0
- package/plugins/dsh/src/delphi.test.ts +222 -0
- package/plugins/dsh/src/delphi.ts +247 -0
- package/plugins/dsh/src/dialect.test.ts +59 -0
- package/plugins/dsh/src/dialect.ts +40 -0
- package/plugins/dsh/src/index.ts +70 -4
- package/plugins/dsh/src/skills.test.ts +26 -19
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/opencode/skills/delphi-review/SKILL.md +98 -14
- package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/opencode/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/qoder/agents/delphi-architecture.md +1 -1
- package/plugins/qoder/agents/delphi-feasibility.md +1 -1
- package/plugins/qoder/agents/delphi-technical.md +1 -1
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/qoder/skills/delphi-review/SKILL.md +98 -14
- package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/qoder/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
- package/principles/AGENTS.md +9 -8
- package/principles/__tests__/adapter-metrics.test.ts +292 -0
- package/principles/__tests__/analyzer.test.ts +29 -6
- package/principles/__tests__/baseline-prune-timeout.test.ts +71 -0
- package/principles/__tests__/baseline-prune.test.ts +338 -0
- package/principles/__tests__/boy-scout-init-baseline.test.ts +167 -0
- package/principles/__tests__/boy-scout-integration.test.ts +97 -6
- package/principles/__tests__/boy-scout.test.ts +253 -7
- package/principles/__tests__/config-enforcement.test.ts +469 -0
- package/principles/__tests__/config-git-timeout.test.ts +66 -0
- package/principles/__tests__/direct-execution.test.ts +187 -0
- package/principles/__tests__/index.test.ts +16 -3
- package/principles/__tests__/warnings-baseline-budgets.test.ts +74 -0
- package/principles/adapters/typescript.ts +613 -13
- package/principles/analyzer.ts +22 -0
- package/principles/baseline-prune.ts +217 -0
- package/principles/boy-scout.ts +516 -56
- package/principles/config.ts +362 -54
- package/principles/direct-execution.ts +60 -0
- package/principles/index.ts +40 -17
- package/principles/rules/__tests__/clean-code/many-exports.test.ts +16 -10
- package/principles/rules/clean-code/code-duplication.ts +8 -8
- package/principles/rules/clean-code/deep-nesting.ts +8 -8
- package/principles/rules/clean-code/god-class.ts +8 -8
- package/principles/rules/clean-code/large-file.ts +15 -11
- package/principles/rules/clean-code/long-function.ts +8 -8
- package/principles/rules/clean-code/magic-numbers.ts +10 -7
- package/principles/rules/clean-code/many-exports.ts +7 -7
- package/principles/rules/clean-code/missing-error-handling.ts +5 -5
- package/principles/rules/clean-code/too-many-params.ts +8 -8
- package/principles/rules/clean-code/unused-imports.ts +5 -5
- package/principles/rules/solid/dip.ts +18 -8
- package/principles/rules/solid/isp.ts +9 -8
- package/principles/rules/solid/lsp.ts +6 -6
- package/principles/rules/solid/ocp.ts +6 -5
- package/principles/rules/solid/srp.ts +7 -8
- package/principles/types.ts +15 -0
- package/scripts/__tests__/sync-package-content.test.js +28 -0
- package/scripts/delphi-external-review.cjs +193 -10
- package/scripts/sync-package-content.js +20 -10
- package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/skills/delphi-review/AGENTS.md +4 -4
- package/skills/delphi-review/SKILL.md +98 -14
- package/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/skills/delphi-review/references/requirements.md +6 -6
- package/skills/sprint-flow/AGENTS.md +4 -4
- package/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/skills/test-specification-alignment/AGENTS.md +4 -4
- package/lib/__tests__/download-skill.test.js +0 -281
- package/lib/download-skill.js +0 -96
package/lib/shared-utils.js
CHANGED
|
@@ -34,7 +34,7 @@ function readXpGateConfig() {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function copyHooks(srcDir, destDir) {
|
|
37
|
-
['pre-commit', 'pre-push'].forEach(hook => {
|
|
37
|
+
['pre-commit', 'pre-push', 'post-merge'].forEach(hook => {
|
|
38
38
|
const src = path.join(srcDir, 'hooks', hook);
|
|
39
39
|
const dest = path.join(destDir, hook);
|
|
40
40
|
if (fs.existsSync(src)) {
|
|
@@ -64,7 +64,7 @@ function copyAdapters(srcDir, destDir) {
|
|
|
64
64
|
}
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
|
-
const githooksDir = path.resolve(srcDir, '..', '..', '
|
|
67
|
+
const githooksDir = path.resolve(srcDir, '..', '..', 'githooks');
|
|
68
68
|
if (fs.existsSync(githooksDir)) {
|
|
69
69
|
fs.readdirSync(githooksDir).forEach(f => {
|
|
70
70
|
if ((f.startsWith('gate-') || f === 'sprint-gate.sh') && f.endsWith('.sh')) {
|
|
@@ -72,6 +72,50 @@ function copyAdapters(srcDir, destDir) {
|
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
|
+
// Gate scripts ship at the package root — sync-package-content.js copies them
|
|
76
|
+
// from githooks/ during prepack. Installed environments have no repo-level
|
|
77
|
+
// githooks/, so the package root is the only reliable source.
|
|
78
|
+
if (fs.existsSync(srcDir)) {
|
|
79
|
+
fs.readdirSync(srcDir).forEach(f => {
|
|
80
|
+
if ((f.startsWith('gate-') || f === 'sprint-gate.sh') && f.endsWith('.sh')) {
|
|
81
|
+
fs.copyFileSync(path.join(srcDir, f), path.join(destDir, f));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The flags each skill command consumes. A flag outside that command's list used
|
|
89
|
+
* to be dropped silently, so `xp-gate install-skill <name> --offline` — the form
|
|
90
|
+
* the CLI documented until the bundle-only installer (#416) removed it — exited 0
|
|
91
|
+
* having done nothing with the flag. Same defect class as the one #488 fixed for
|
|
92
|
+
* `doctor --sync-hooks`: an ignored option must be answered loudly. A flag that
|
|
93
|
+
* parses but is never destructured by the handler is ignored too, so the table is
|
|
94
|
+
* keyed per command (Round 2 architecture MI-04).
|
|
95
|
+
*/
|
|
96
|
+
const SKILL_FLAGS_BY_COMMAND = {
|
|
97
|
+
'install-skill': ['--verbose', '--force'],
|
|
98
|
+
'update-skill': ['--verbose', '--all', '--check'],
|
|
99
|
+
'uninstall-skill': ['--force'],
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {string[]} args - arguments after the skill name
|
|
104
|
+
* @param {string} command - the subcommand whose flags are accepted
|
|
105
|
+
* @returns {{options: Object.<string, boolean>, unknown: string[]}}
|
|
106
|
+
*/
|
|
107
|
+
function parseSkillFlags(args, command) {
|
|
108
|
+
const options = { verbose: false, force: false, all: false, check: false };
|
|
109
|
+
const allowed = SKILL_FLAGS_BY_COMMAND[command] || [];
|
|
110
|
+
const unknown = [];
|
|
111
|
+
for (const arg of args) {
|
|
112
|
+
if (!allowed.includes(arg)) {
|
|
113
|
+
unknown.push(arg);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
options[arg.slice(2)] = true;
|
|
117
|
+
}
|
|
118
|
+
return { options, unknown };
|
|
75
119
|
}
|
|
76
120
|
|
|
77
|
-
module.exports = { copyDirRecursive, readXpGateConfig, copyHooks, copyAdapters };
|
|
121
|
+
module.exports = { copyDirRecursive, readXpGateConfig, copyHooks, copyAdapters, SKILL_FLAGS_BY_COMMAND, parseSkillFlags };
|
package/lib/uninstall.js
CHANGED
|
@@ -19,6 +19,7 @@ const BACKUP_DIR = path.join(CONFIG_DIR, '.uninstall-backup');
|
|
|
19
19
|
const SIGNATURES = {
|
|
20
20
|
'pre-commit': 'OpenCode Quality Gates - Pre-Commit Hook',
|
|
21
21
|
'pre-push': 'Pre-push Hook - Code Walkthrough Result Validator',
|
|
22
|
+
'post-merge': 'XP-Gate VERSION sync hook',
|
|
22
23
|
'adapter-common.sh': 'detect_project_lang()'
|
|
23
24
|
};
|
|
24
25
|
|
|
@@ -201,6 +202,14 @@ function buildPlan(mode) {
|
|
|
201
202
|
manifestKey: '.git/hooks/pre-push',
|
|
202
203
|
critical: true
|
|
203
204
|
});
|
|
205
|
+
plan.push({
|
|
206
|
+
type: 'file',
|
|
207
|
+
path: path.join(hooksDir, 'post-merge'),
|
|
208
|
+
signature: SIGNATURES['post-merge'],
|
|
209
|
+
label: '.git/hooks/post-merge',
|
|
210
|
+
manifestKey: '.git/hooks/post-merge',
|
|
211
|
+
critical: true
|
|
212
|
+
});
|
|
204
213
|
plan.push({
|
|
205
214
|
type: 'file',
|
|
206
215
|
path: path.join(githooksDir, 'adapter-common.sh'),
|
|
@@ -489,6 +498,9 @@ async function uninstall(args) {
|
|
|
489
498
|
|
|
490
499
|
// Print summary
|
|
491
500
|
console.log('\nUninstall complete');
|
|
501
|
+
console.log('Note: Qoder Delphi expert agents (.qoder/agents/delphi-*.md) are user-owned files');
|
|
502
|
+
console.log(' that init never overwrites, so they were left in place — delete them by hand');
|
|
503
|
+
console.log(' (then restart the Qoder session) if you want them gone.');
|
|
492
504
|
if (hadErrors) {
|
|
493
505
|
console.log('Some items could not be removed. Run doctor for diagnostics.');
|
|
494
506
|
}
|
package/lib/update-hooks.js
CHANGED
|
@@ -38,8 +38,8 @@ function getProjectHooksDir() {
|
|
|
38
38
|
function detectLocalModifications(srcDir, hooksDestDir, adaptersDestDir) {
|
|
39
39
|
const modified = [];
|
|
40
40
|
|
|
41
|
-
// Check hook files: pre-commit, pre-push
|
|
42
|
-
['pre-commit', 'pre-push'].forEach(hook => {
|
|
41
|
+
// Check hook files: pre-commit, pre-push, post-merge
|
|
42
|
+
['pre-commit', 'pre-push', 'post-merge'].forEach(hook => {
|
|
43
43
|
const srcPath = path.join(srcDir, 'hooks', hook);
|
|
44
44
|
const destPath = path.join(hooksDestDir, hook);
|
|
45
45
|
if (fs.existsSync(destPath) && fs.existsSync(srcPath)) {
|
|
@@ -150,7 +150,7 @@ function atomicCopyFile(src, dest, dryRun, noBackup, label) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/**
|
|
153
|
-
* Copy hook files (pre-commit, pre-push) from package to destination.
|
|
153
|
+
* Copy hook files (pre-commit, pre-push, post-merge) from package to destination.
|
|
154
154
|
* @param {string} srcDir - Package root directory
|
|
155
155
|
* @param {string} destDir - Destination hooks directory
|
|
156
156
|
* @param {boolean} dryRun
|
|
@@ -158,7 +158,7 @@ function atomicCopyFile(src, dest, dryRun, noBackup, label) {
|
|
|
158
158
|
*/
|
|
159
159
|
function copyHooks(srcDir, destDir, dryRun, noBackup) {
|
|
160
160
|
const hooksSrcDir = path.join(srcDir, 'hooks');
|
|
161
|
-
['pre-commit', 'pre-push'].forEach(hook => {
|
|
161
|
+
['pre-commit', 'pre-push', 'post-merge'].forEach(hook => {
|
|
162
162
|
const src = path.join(hooksSrcDir, hook);
|
|
163
163
|
const dest = path.join(destDir, hook);
|
|
164
164
|
atomicCopyFile(src, dest, dryRun, noBackup, hook);
|
package/lib/update-skill.js
CHANGED
|
@@ -6,7 +6,6 @@ const os = require('os');
|
|
|
6
6
|
const HOME = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
7
7
|
|
|
8
8
|
const CONFIG_DIR = path.join(HOME, '.config', 'xp-gate');
|
|
9
|
-
const SKILLS_DIR = path.join(HOME, '.config', 'opencode', 'skills');
|
|
10
9
|
|
|
11
10
|
function handleCheckMode() {
|
|
12
11
|
console.log('Checking for updates...');
|
|
@@ -59,12 +58,12 @@ async function updateSkill(name, options = {}) {
|
|
|
59
58
|
|
|
60
59
|
async function updateSingleSkill(name, verbose) {
|
|
61
60
|
console.log(`Updating ${name}...`);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
|
|
62
|
+
// Everything destructive lives behind installSkill()'s validation: it resolves
|
|
63
|
+
// the platform skills directory, backs the existing copy up, and only then
|
|
64
|
+
// replaces it. Deleting here first (#416) wiped the wrong copy on Qoder and
|
|
65
|
+
// Claude Code, and turned an update of a skill the package no longer carries
|
|
66
|
+
// into an unrecoverable loss.
|
|
68
67
|
const { installSkill } = require('./install-skill.js');
|
|
69
68
|
const result = await installSkill(name, { force: true, verbose });
|
|
70
69
|
|
package/mock-policy/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MOCK-POLICY KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.
|
|
3
|
+
**Generated:** 2026-10-08
|
|
4
|
+
**Commit:** unknown
|
|
5
|
+
**Branch:** unknown
|
|
6
|
+
**Version:** 0.20.1.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Mock layering policy enforcement — Gate M3 of pre-push hook. Ensures integration tests use real implementations for internal dependencies, mock external dependencies, and annotate pending mocks with removal plans. Combines project scope scanning, mock decision engine, and per-file validation into a single pipeline.
|
package/mutation/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MUTATION KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.
|
|
3
|
+
**Generated:** 2026-10-08
|
|
4
|
+
**Commit:** unknown
|
|
5
|
+
**Branch:** unknown
|
|
6
|
+
**Version:** 0.20.1.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**Gate M** (incremental mutation testing) + **Gate M2** helpers (test-layer detection used by `src/mock-policy/`). Pre-push quality gate. TypeScript-only; uses Stryker.
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
import { describe, it, expect, vi } from 'vitest';
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
|
|
10
|
-
//
|
|
11
|
-
//
|
|
10
|
+
// The REAL filter is under test: the previous local duplicate diverged from
|
|
11
|
+
// gate-m.ts (which is multi-language via registered runners) and could not
|
|
12
|
+
// catch #480 for that exact reason.
|
|
13
|
+
import { filterSourceFiles } from '../gate-m';
|
|
12
14
|
import { detectAITestCharacteristics } from '../detect-ai-test';
|
|
13
15
|
|
|
14
16
|
/* jscpd:disable */
|
|
@@ -56,16 +58,6 @@ function parseArgs(args: string[]): {
|
|
|
56
58
|
}
|
|
57
59
|
/* jscpd:enable */
|
|
58
60
|
|
|
59
|
-
function filterSourceFiles(files: string[]): string[] {
|
|
60
|
-
return files.filter(file => {
|
|
61
|
-
if (!file.endsWith('.ts')) return false;
|
|
62
|
-
if (file.endsWith('.test.ts')) return false;
|
|
63
|
-
if (file.endsWith('.d.ts')) return false;
|
|
64
|
-
if (file.includes('/adapters/')) return false;
|
|
65
|
-
return true;
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
61
|
vi.mock('fs/promises');
|
|
70
62
|
|
|
71
63
|
describe('gate-m.ts - Mutation Testing Gate', () => {
|
|
@@ -126,11 +118,71 @@ describe('gate-m.ts - Mutation Testing Gate', () => {
|
|
|
126
118
|
expect(result).toEqual(['src/foo.ts']);
|
|
127
119
|
});
|
|
128
120
|
|
|
129
|
-
it('should filter out
|
|
121
|
+
it('should filter out files with no registered runner', () => {
|
|
130
122
|
const files = ['src/foo.ts', 'src/bar.js', 'src/baz.py'];
|
|
131
123
|
const result = filterSourceFiles(files);
|
|
132
124
|
|
|
133
|
-
|
|
125
|
+
// .ts (stryker) and .py (mutmut) have registered runners; .js has none.
|
|
126
|
+
expect(result).toEqual(['src/foo.ts', 'src/baz.py']);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should filter out test-tree helpers not named *.test.* (#480)', () => {
|
|
130
|
+
const files = [
|
|
131
|
+
'tests/e2e/helpers/e2e-server.ts',
|
|
132
|
+
'test/fixtures/setup.py',
|
|
133
|
+
'src/__tests__/harness.ts',
|
|
134
|
+
'tests\\e2e\\helpers\\e2e-server.ts',
|
|
135
|
+
];
|
|
136
|
+
const result = filterSourceFiles(files);
|
|
137
|
+
|
|
138
|
+
expect(result).toEqual([]);
|
|
139
|
+
|
|
140
|
+
// The same exclusion inside a list that also carries production source:
|
|
141
|
+
// `a/b/tests/` is a tree segment, and src/foo.ts must survive next to it.
|
|
142
|
+
expect(filterSourceFiles(['src/foo.ts', 'a/b/tests/fixture-factory.ts'])).toEqual(['src/foo.ts']);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Round 2 feasibility FC-03: only the test-tree regex read `normalized`, the
|
|
146
|
+
// `.test.`/`_test.`, Java/Kotlin and /adapters/ checks read the raw path, so a
|
|
147
|
+
// backslash-delimited path was judged by two different views of one string.
|
|
148
|
+
it('should judge every exclusion against one separator view of the path (#480)', () => {
|
|
149
|
+
for (const files of [
|
|
150
|
+
['src\\adapters\\router.ts'],
|
|
151
|
+
['src\\FooTest.java'],
|
|
152
|
+
['src/FooTest.java'],
|
|
153
|
+
['src/PaymentTests.kt'],
|
|
154
|
+
['src/BillingSpec.kts'],
|
|
155
|
+
['src\\helper_test.go'],
|
|
156
|
+
['src\\__tests__\\harness.ts'],
|
|
157
|
+
['src\\legacy.d.ts'],
|
|
158
|
+
]) {
|
|
159
|
+
expect(filterSourceFiles(files)).toEqual([]);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// The same class as #480: pre-push excludes *.spec.* from the mutation
|
|
164
|
+
// candidate list, gate-m.ts did not, so the two definitions of "not
|
|
165
|
+
// production code" already disagreed on spec-named test files.
|
|
166
|
+
it('should filter out *.spec.* test files, as pre-push already does (#480)', () => {
|
|
167
|
+
expect(filterSourceFiles(['src/login.spec.ts'])).toEqual([]);
|
|
168
|
+
expect(filterSourceFiles(['src\\login.spec.ts'])).toEqual([]);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should not exclude production paths that merely contain "test" as a substring (#480)', () => {
|
|
172
|
+
// Round 2 technical MN-02 asked whether the tree segment is anchored: it is,
|
|
173
|
+
// so only a whole path segment named test/tests/__tests__ excludes.
|
|
174
|
+
const files = [
|
|
175
|
+
'src/latest/foo.ts',
|
|
176
|
+
'src/protest/foo.ts',
|
|
177
|
+
'src/test-utils/foo.ts',
|
|
178
|
+
'src/mytests/foo.ts',
|
|
179
|
+
'testing/reporter.ts',
|
|
180
|
+
'src/test-data.ts',
|
|
181
|
+
'tests-helper.ts',
|
|
182
|
+
];
|
|
183
|
+
const result = filterSourceFiles(files);
|
|
184
|
+
|
|
185
|
+
expect(result).toEqual(files);
|
|
134
186
|
});
|
|
135
187
|
|
|
136
188
|
it('should return empty array when no source files', () => {
|
|
@@ -32,6 +32,61 @@ describe('MutmutRunner', () => {
|
|
|
32
32
|
let MutmutRunner: typeof import('../runners/mutmut-runner').MutmutRunner;
|
|
33
33
|
let tmpDir: string;
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Wait until the runner has actually spawned the child process.
|
|
37
|
+
*
|
|
38
|
+
* `run()` awaits `isAvailable()` when the platform is win32 (the route is
|
|
39
|
+
* resolved via a version probe), so `spawn` is NOT called synchronously with
|
|
40
|
+
* `run()`. Calling `stdoutCb` right after `run()` therefore raced on Windows:
|
|
41
|
+
* the callback had not been captured yet and the test threw
|
|
42
|
+
* `stdoutCb is not a function`. Linux skipped the await entirely, which is why
|
|
43
|
+
* CI stayed green while the same suite failed locally.
|
|
44
|
+
*/
|
|
45
|
+
async function waitForSpawn(): Promise<void> {
|
|
46
|
+
for (let i = 0; i < 50 && !vi.mocked(spawn).mock.calls.length; i++) {
|
|
47
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build a fake child process whose `stdout`/`close` callbacks are captured so a
|
|
53
|
+
* test can drive them by hand. Shared because every spawn-based test needs the
|
|
54
|
+
* same shape, and inlining it in each one pushed those callbacks past the
|
|
55
|
+
* function-length rule.
|
|
56
|
+
*/
|
|
57
|
+
function mockChildProcess(capture: {
|
|
58
|
+
stdout?: (cb: (d: Buffer) => void) => void;
|
|
59
|
+
stderr?: (cb: (d: Buffer) => void) => void;
|
|
60
|
+
close?: (cb: (code: number | null) => void) => void;
|
|
61
|
+
/** Run when `close` is subscribed, instead of the test driving it by hand. */
|
|
62
|
+
onClose?: (emit: { stderr: (d: Buffer) => void; close: (code: number) => void }) => void;
|
|
63
|
+
}) {
|
|
64
|
+
let stderrCb: ((d: Buffer) => void) | null = null;
|
|
65
|
+
let closeCb: ((code: number | null) => void) | null = null;
|
|
66
|
+
return {
|
|
67
|
+
stdout: {
|
|
68
|
+
on: vi.fn((_event: string, cb: (d: Buffer) => void) => capture.stdout?.(cb)),
|
|
69
|
+
},
|
|
70
|
+
stderr: {
|
|
71
|
+
on: vi.fn((_event: string, cb: (d: Buffer) => void) => {
|
|
72
|
+
stderrCb = cb;
|
|
73
|
+
capture.stderr?.(cb);
|
|
74
|
+
}),
|
|
75
|
+
},
|
|
76
|
+
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
77
|
+
if (event !== 'close') return;
|
|
78
|
+
closeCb = cb;
|
|
79
|
+
capture.close?.(cb);
|
|
80
|
+
capture.onClose?.({
|
|
81
|
+
stderr: d => stderrCb?.(d),
|
|
82
|
+
close: code => closeCb?.(code),
|
|
83
|
+
});
|
|
84
|
+
}),
|
|
85
|
+
kill: vi.fn(),
|
|
86
|
+
killed: false,
|
|
87
|
+
} as unknown as ChildProcess;
|
|
88
|
+
}
|
|
89
|
+
|
|
35
90
|
beforeEach(async () => {
|
|
36
91
|
vi.clearAllMocks();
|
|
37
92
|
vi.mocked(execSync).mockReset();
|
|
@@ -140,20 +195,12 @@ describe('MutmutRunner', () => {
|
|
|
140
195
|
let stdoutCb: ((d: Buffer) => void) | null = null;
|
|
141
196
|
let closeCb: ((code: number | null) => void) | null = null;
|
|
142
197
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}),
|
|
148
|
-
},
|
|
149
|
-
stderr: { on: vi.fn() },
|
|
150
|
-
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
151
|
-
if (event === 'close') closeCb = cb;
|
|
198
|
+
vi.mocked(spawn).mockReturnValue(
|
|
199
|
+
mockChildProcess({
|
|
200
|
+
stdout: cb => { stdoutCb = cb; },
|
|
201
|
+
close: cb => { closeCb = cb; },
|
|
152
202
|
}),
|
|
153
|
-
|
|
154
|
-
killed: false,
|
|
155
|
-
};
|
|
156
|
-
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
203
|
+
);
|
|
157
204
|
|
|
158
205
|
// No existing pyproject.toml
|
|
159
206
|
vi.mocked(existsSync).mockReturnValue(false);
|
|
@@ -165,9 +212,11 @@ describe('MutmutRunner', () => {
|
|
|
165
212
|
cwd: tmpDir,
|
|
166
213
|
});
|
|
167
214
|
|
|
168
|
-
// Simulate stdout with emoji progress
|
|
215
|
+
// Simulate stdout with emoji progress. One wait covers both callbacks --
|
|
216
|
+
// they are captured by the same spawn invocation.
|
|
169
217
|
const progressOutput =
|
|
170
218
|
'38/38 🎉 9 🫥 29 ⏰ 0 🤔 0 🙁 0 🔇 0 🧙 0\n';
|
|
219
|
+
await waitForSpawn();
|
|
171
220
|
stdoutCb!(Buffer.from(progressOutput));
|
|
172
221
|
closeCb!(0);
|
|
173
222
|
|
|
@@ -211,6 +260,7 @@ describe('MutmutRunner', () => {
|
|
|
211
260
|
timeoutMs: 60000,
|
|
212
261
|
cwd: 'C:\\work\\project',
|
|
213
262
|
});
|
|
263
|
+
await waitForSpawn();
|
|
214
264
|
closeCb!(0);
|
|
215
265
|
await promise;
|
|
216
266
|
|
|
@@ -249,6 +299,7 @@ describe('MutmutRunner', () => {
|
|
|
249
299
|
timeoutMs: 60000,
|
|
250
300
|
cwd: 'C:\\work\\project',
|
|
251
301
|
});
|
|
302
|
+
await waitForSpawn();
|
|
252
303
|
closeCb!(0);
|
|
253
304
|
await promise;
|
|
254
305
|
|
|
@@ -285,6 +336,7 @@ describe('MutmutRunner', () => {
|
|
|
285
336
|
timeoutMs: 60000,
|
|
286
337
|
cwd: 'C:\\work dir\\safe; echo injected',
|
|
287
338
|
});
|
|
339
|
+
await waitForSpawn();
|
|
288
340
|
closeCb!(0);
|
|
289
341
|
await promise;
|
|
290
342
|
|
|
@@ -320,6 +372,7 @@ describe('MutmutRunner', () => {
|
|
|
320
372
|
const runner = new MutmutRunner('win32');
|
|
321
373
|
expect(await runner.isAvailable()).toBe(true);
|
|
322
374
|
const promise = runner.run({ files: ['src/main.py'], timeoutMs: 60000, cwd: windowsPath });
|
|
375
|
+
await waitForSpawn();
|
|
323
376
|
closeCb!(0);
|
|
324
377
|
await promise;
|
|
325
378
|
|
|
@@ -368,6 +421,7 @@ describe('MutmutRunner', () => {
|
|
|
368
421
|
nativeAvailable = false;
|
|
369
422
|
expect(await runner.isAvailable()).toBe(true);
|
|
370
423
|
const promise = runner.run({ files: ['src/main.py'], timeoutMs: 60000, cwd: 'C:\\repo' });
|
|
424
|
+
await waitForSpawn();
|
|
371
425
|
closeCb!(0);
|
|
372
426
|
await promise;
|
|
373
427
|
|
|
@@ -489,6 +543,7 @@ describe('MutmutRunner', () => {
|
|
|
489
543
|
expect(await runner.isAvailable()).toBe(true);
|
|
490
544
|
const promise = runner.run({ files: ['src/main.py'], timeoutMs: 100, cwd: 'C:\\repo' });
|
|
491
545
|
await vi.advanceTimersByTimeAsync(100);
|
|
546
|
+
await waitForSpawn();
|
|
492
547
|
closeCb!(1);
|
|
493
548
|
const outcome = await promise;
|
|
494
549
|
|
|
@@ -622,26 +677,18 @@ describe('MutmutRunner', () => {
|
|
|
622
677
|
});
|
|
623
678
|
|
|
624
679
|
it('should return error message when exit code is non-zero', async () => {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
stderrCb = cb;
|
|
631
|
-
}),
|
|
632
|
-
},
|
|
633
|
-
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
634
|
-
if (event === 'close') {
|
|
680
|
+
// The stderr line and the non-zero exit must arrive together, so the
|
|
681
|
+
// factory emits both as soon as the runner subscribes to `close`.
|
|
682
|
+
vi.mocked(spawn).mockReturnValue(
|
|
683
|
+
mockChildProcess({
|
|
684
|
+
onClose: emit => {
|
|
635
685
|
setTimeout(() => {
|
|
636
|
-
|
|
637
|
-
|
|
686
|
+
emit.stderr(Buffer.from('mutation failed'));
|
|
687
|
+
emit.close(1);
|
|
638
688
|
}, 10);
|
|
639
|
-
}
|
|
689
|
+
},
|
|
640
690
|
}),
|
|
641
|
-
|
|
642
|
-
killed: false,
|
|
643
|
-
};
|
|
644
|
-
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
691
|
+
);
|
|
645
692
|
vi.mocked(existsSync).mockReturnValue(false);
|
|
646
693
|
|
|
647
694
|
const runner = new MutmutRunner();
|
|
@@ -685,6 +732,7 @@ describe('MutmutRunner', () => {
|
|
|
685
732
|
});
|
|
686
733
|
|
|
687
734
|
const output = '20/20 🎉 10 🫥 5 ⏰ 2 🤔 1 🙁 2 🔇 0 🧙 0\n';
|
|
735
|
+
await waitForSpawn();
|
|
688
736
|
stdoutCb!(Buffer.from(output));
|
|
689
737
|
closeCb!(0);
|
|
690
738
|
|
|
@@ -724,9 +772,11 @@ describe('MutmutRunner', () => {
|
|
|
724
772
|
cwd: tmpDir,
|
|
725
773
|
});
|
|
726
774
|
|
|
775
|
+
await waitForSpawn();
|
|
776
|
+
|
|
727
777
|
stdoutCb!(Buffer.from('Some random output without emoji\n'));
|
|
778
|
+
await waitForSpawn();
|
|
728
779
|
closeCb!(0);
|
|
729
|
-
|
|
730
780
|
const result = await promise;
|
|
731
781
|
expect(result.report).toBeNull();
|
|
732
782
|
expect(result.timedOut).toBe(false);
|
|
@@ -760,6 +810,7 @@ describe('MutmutRunner', () => {
|
|
|
760
810
|
});
|
|
761
811
|
|
|
762
812
|
const output = '0/0 🎉 0 🫥 0 ⏰ 0 🤔 0 🙁 0 🔇 0 🧙 0\n';
|
|
813
|
+
await waitForSpawn();
|
|
763
814
|
stdoutCb!(Buffer.from(output));
|
|
764
815
|
closeCb!(0);
|
|
765
816
|
|
|
@@ -789,6 +840,7 @@ describe('MutmutRunner', () => {
|
|
|
789
840
|
timeoutMs: 60000,
|
|
790
841
|
cwd: tmpDir,
|
|
791
842
|
});
|
|
843
|
+
await waitForSpawn();
|
|
792
844
|
closeCb!(0);
|
|
793
845
|
await promise;
|
|
794
846
|
|
|
@@ -839,6 +891,7 @@ describe('MutmutRunner', () => {
|
|
|
839
891
|
timeoutMs: 60000,
|
|
840
892
|
cwd: tmpDir,
|
|
841
893
|
});
|
|
894
|
+
await waitForSpawn();
|
|
842
895
|
closeCb!(0);
|
|
843
896
|
await promise;
|
|
844
897
|
|
|
@@ -888,6 +941,7 @@ describe('MutmutRunner', () => {
|
|
|
888
941
|
timeoutMs: 60000,
|
|
889
942
|
cwd: tmpDir,
|
|
890
943
|
});
|
|
944
|
+
await waitForSpawn();
|
|
891
945
|
closeCb!(0);
|
|
892
946
|
await promise;
|
|
893
947
|
|
|
@@ -905,16 +959,9 @@ describe('MutmutRunner', () => {
|
|
|
905
959
|
describe('cleanup', () => {
|
|
906
960
|
it('should restore backup after run completes', async () => {
|
|
907
961
|
let closeCb: ((code: number | null) => void) | null = null;
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
912
|
-
if (event === 'close') closeCb = cb;
|
|
913
|
-
}),
|
|
914
|
-
kill: vi.fn(),
|
|
915
|
-
killed: false,
|
|
916
|
-
};
|
|
917
|
-
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
962
|
+
vi.mocked(spawn).mockReturnValue(
|
|
963
|
+
mockChildProcess({ close: cb => { closeCb = cb; } }),
|
|
964
|
+
);
|
|
918
965
|
|
|
919
966
|
vi.mocked(existsSync).mockImplementation((path) => {
|
|
920
967
|
const p = path.toString();
|
|
@@ -939,6 +986,7 @@ describe('MutmutRunner', () => {
|
|
|
939
986
|
timeoutMs: 60000,
|
|
940
987
|
cwd: tmpDir,
|
|
941
988
|
});
|
|
989
|
+
await waitForSpawn();
|
|
942
990
|
closeCb!(0);
|
|
943
991
|
await promise;
|
|
944
992
|
|
|
@@ -989,6 +1037,7 @@ describe('MutmutRunner', () => {
|
|
|
989
1037
|
timeoutMs: 60000,
|
|
990
1038
|
cwd: tmpDir,
|
|
991
1039
|
});
|
|
1040
|
+
await waitForSpawn();
|
|
992
1041
|
closeCb!(0);
|
|
993
1042
|
await promise;
|
|
994
1043
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-480
|
|
3
|
+
* @intent "测试基础设施" 的定义在 githooks/pre-push(bash)与 src/mutation/gate-m.ts
|
|
4
|
+
* (TypeScript)各写了一遍,没有共享常量的自然机制;一侧演化后 Gate M 与
|
|
5
|
+
* pre-push 会对同一变更集给出不一致裁决(Round 2 架构 MA-02 / 可行性 FC-03)。
|
|
6
|
+
* 跨语言无法共用函数,退而求其次:把两处文本钉成同一份定义。
|
|
7
|
+
* @covers AC-480-04
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect } from 'vitest';
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
|
|
13
|
+
const repoRoot = path.resolve(__dirname, '..', '..', '..');
|
|
14
|
+
const prePush = fs.readFileSync(path.join(repoRoot, 'githooks', 'pre-push'), 'utf8');
|
|
15
|
+
const gateM = fs.readFileSync(path.join(repoRoot, 'src', 'mutation', 'gate-m.ts'), 'utf8');
|
|
16
|
+
|
|
17
|
+
const prePushFilter =
|
|
18
|
+
prePush.split('\n').find((line) => line.includes('CHANGED_SOURCE_FILES=')) || '';
|
|
19
|
+
|
|
20
|
+
describe('test-path filter parity between pre-push and gate-m (#480)', () => {
|
|
21
|
+
it('compares a real filter line, not an empty string', () => {
|
|
22
|
+
expect(prePushFilter).toContain('grep -v');
|
|
23
|
+
expect(gateM).toContain('export function filterSourceFiles');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('both sides exclude the same test-tree segments', () => {
|
|
27
|
+
expect(prePushFilter).toContain('(tests?|__tests__)/');
|
|
28
|
+
expect(gateM).toContain(String.raw`(^|\/)(tests?|__tests__)\/`);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('both sides exclude declaration files and spec-named tests', () => {
|
|
32
|
+
expect(prePushFilter).toContain(String.raw`\.d\.ts$`);
|
|
33
|
+
expect(prePushFilter).toContain(String.raw`\.spec\.`);
|
|
34
|
+
expect(gateM).toContain("endsWith('.d.ts')");
|
|
35
|
+
expect(gateM).toContain("includes('.spec.')");
|
|
36
|
+
});
|
|
37
|
+
});
|
package/mutation/gate-m.ts
CHANGED
|
@@ -79,19 +79,32 @@ function parseTimeout(options: GateMOptions, args: string[], i: number): void {
|
|
|
79
79
|
// ── File filtering: excludes tests, declarations, adapters ──
|
|
80
80
|
// Delegate per-language filtering to a helper.
|
|
81
81
|
|
|
82
|
-
function filterSourceFiles(files: string[]): string[] {
|
|
82
|
+
export function filterSourceFiles(files: string[]): string[] {
|
|
83
83
|
return files.filter(file => {
|
|
84
84
|
const ext = path.extname(file);
|
|
85
|
+
// One separator view for every pattern below: the path arrives from the
|
|
86
|
+
// adapter layer, which on Windows yields backslash-delimited relative paths.
|
|
87
|
+
const normalized = file.replace(/\\/g, '/');
|
|
85
88
|
// Skip test files
|
|
86
|
-
if (
|
|
87
|
-
// Skip
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
89
|
+
if (normalized.includes('.test.') || normalized.includes('_test.')) return false;
|
|
90
|
+
// Skip spec-named test files: the pre-push filter already excludes them, and
|
|
91
|
+
// a mutant inside a *.spec.ts is scored against a production budget (#480).
|
|
92
|
+
if (normalized.includes('.spec.')) return false;
|
|
93
|
+
// Skip test-tree directories: helpers/fixtures/factories there are test
|
|
94
|
+
// infrastructure judged by the suites that run them, not by mutation
|
|
95
|
+
// scoring budgeted for production code (#480)
|
|
96
|
+
if (/(^|\/)(tests?|__tests__)\//.test(normalized)) return false;
|
|
97
|
+
// Skip Java/Kotlin test files: FooTest.java, FooSpec.kt, etc. Case-sensitive
|
|
98
|
+
// "Test"/"Spec"/"IT" suffix matching is what the Surefire/Failsafe conventions
|
|
99
|
+
// actually produce -- `[A-Z]Test\.only` caught MyTest.java but not FooTest.java,
|
|
100
|
+
// so most test classes stayed in the mutation candidate list.
|
|
101
|
+
if (/Test\.(java|kt|kts)$/.test(normalized)) return false;
|
|
102
|
+
if (/Tests\.(java|kt|kts)$/.test(normalized)) return false;
|
|
103
|
+
if (/(IT|Spec)\.(java|kt|kts)$/.test(normalized)) return false;
|
|
91
104
|
// Skip declaration files
|
|
92
|
-
if (
|
|
105
|
+
if (normalized.endsWith('.d.ts')) return false;
|
|
93
106
|
// Skip adapter files
|
|
94
|
-
if (
|
|
107
|
+
if (normalized.includes('/adapters/')) return false;
|
|
95
108
|
// Skip files with no registered runner
|
|
96
109
|
const runner = resolveRunner(ext);
|
|
97
110
|
return runner !== undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boyingliu01/xp-gate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"description": "AI-driven development workflow: 6 quality gates + Delphi review + Sprint Flow",
|
|
5
5
|
"bin": {
|
|
6
6
|
"xp-gate": "./bin/xp-gate.js"
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"node": ">=18.0.0"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
|
-
"version": "0.
|
|
45
|
+
"version": "0.20.0",
|
|
46
46
|
"license-check": "node scripts/check-mattpocock-license.cjs",
|
|
47
47
|
"prepack": "node scripts/check-mattpocock-license.cjs && node scripts/sync-package-content.js",
|
|
48
48
|
"test": "echo \"Run tests\" && exit 0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xp-gate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
|
|
6
6
|
"author": {
|