@boyingliu01/xp-gate 0.18.5 → 0.19.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/adapter-common.sh +27 -0
- package/adapters/gate-3.sh +0 -0
- package/adapters/gate-4.sh +0 -0
- package/adapters/gate-7.sh +0 -0
- package/adapters/gate-8.sh +0 -0
- package/adapters/gate-9.sh +0 -0
- package/adapters/powershell.sh +114 -35
- package/adapters/typescript.sh +8 -8
- package/build-integrity/__tests__/import-resolver.test.ts +22 -0
- package/build-integrity/__tests__/orchestrator.test.ts +1 -2
- package/build-integrity/gate-10.ts +7 -0
- package/gate-12-file-hygiene.sh +230 -0
- package/hooks/adapter-common.sh +27 -0
- package/hooks/gate-10.sh +105 -0
- package/hooks/gate-12-file-hygiene.sh +0 -0
- package/hooks/lib/validate-code-walkthrough.cjs +125 -0
- package/hooks/pre-commit +130 -33
- package/hooks/pre-push +15 -91
- package/lib/__tests__/check-version.test.js +67 -46
- package/lib/__tests__/doctor.test.js +7 -6
- package/lib/__tests__/gate-runner.test.js +267 -0
- package/lib/__tests__/init.test.js +32 -0
- package/lib/__tests__/npm-package.test.js +13 -0
- package/lib/__tests__/phase-transition.test.js +604 -12
- package/lib/__tests__/sprint-discovery.test.ts +0 -1
- package/lib/__tests__/sprint-status-rework.test.js +100 -1
- package/lib/__tests__/update-hooks.test.js +143 -10
- package/lib/__tests__/upgrade-exec.test.js +73 -45
- package/lib/__tests__/upgrade-skill-sync.test.js +1 -1
- package/lib/__tests__/upgrade.test.js +12 -11
- package/lib/doctor.js +2 -1
- package/lib/gate-runner.js +68 -23
- package/lib/init.js +34 -5
- package/lib/install-skill.js +1 -0
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/phase-transition.js +203 -23
- package/lib/shared-paths.js +2 -0
- package/lib/sprint-status.js +15 -4
- package/lib/test-alignment.ts +3 -13
- package/lib/update-hooks.js +129 -15
- package/mock-policy/AGENTS.md +4 -4
- package/mock-policy/__tests__/gate-m3.test.ts +0 -5
- package/mock-policy/__tests__/scope-scanner.test.ts +8 -2
- package/mutation/AGENTS.md +3 -3
- package/mutation/__tests__/mutmut-runner.test.ts +443 -2
- package/mutation/__tests__/update-baseline.test.ts +0 -2
- package/mutation/runners/mutmut-runner.ts +67 -17
- package/package.json +9 -3
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/clipboard-vision/SKILL.md +47 -0
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
- package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.sh +161 -0
- package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/claude-code/skills/delphi-review/.delphi-config.json.example +24 -27
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +12 -11
- package/plugins/claude-code/skills/delphi-review/INSTALL.md +15 -17
- package/plugins/claude-code/skills/delphi-review/SKILL.md +30 -29
- package/plugins/claude-code/skills/delphi-review/opencode.json.delphi.example +6 -10
- package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +33 -49
- package/plugins/claude-code/skills/delphi-review/references/requirements.md +66 -54
- package/plugins/claude-code/skills/delphi-review/references/round-templates.md +7 -7
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/SKILL.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/references/components/skill-invocations.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/references/force-levels.md +37 -35
- package/plugins/claude-code/skills/sprint-flow/references/orchestration-rules.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/references/phase-1-prep.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +24 -14
- package/plugins/claude-code/skills/sprint-flow/references/phase-4-verify.md +6 -6
- package/plugins/claude-code/skills/sprint-flow/references/phase-overview.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/dsh/README.md +25 -0
- package/plugins/dsh/cordis.patch.yml +7 -0
- package/plugins/dsh/package.json +70 -0
- package/plugins/dsh/scripts/prepack.cjs +82 -0
- package/plugins/dsh/skills/admin-template-guidelines/SKILL.md +113 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-1.md +91 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-2.md +155 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-3.md +70 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-4.md +154 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-5.md +126 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-6.md +180 -0
- package/plugins/dsh/skills/batch-grill-me/SKILL.md +15 -0
- package/plugins/dsh/skills/clipboard-vision/SKILL.md +47 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/dsh/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
- package/plugins/dsh/skills/clipboard-vision/describe-clipboard.sh +161 -0
- package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/dsh/skills/delphi-review/.delphi-config.json.example +70 -0
- package/plugins/dsh/skills/delphi-review/AGENTS.md +67 -0
- package/plugins/dsh/skills/delphi-review/INSTALL.md +150 -0
- package/plugins/dsh/skills/delphi-review/SKILL.md +559 -0
- package/plugins/dsh/skills/delphi-review/hooks.md +19 -0
- package/plugins/dsh/skills/delphi-review/opencode.json.delphi.example +52 -0
- package/plugins/dsh/skills/delphi-review/references/code-walkthrough.md +470 -0
- package/plugins/dsh/skills/delphi-review/references/orchestrator-dispatch.md +64 -0
- package/plugins/dsh/skills/delphi-review/references/requirements.md +402 -0
- package/plugins/dsh/skills/delphi-review/references/round-templates.md +134 -0
- package/plugins/dsh/skills/delphi-review/scripts/verify-consensus.sh +43 -0
- package/plugins/dsh/skills/delphi-review/tools.md +29 -0
- package/plugins/dsh/skills/domain-modeling/ADR-FORMAT.md +64 -0
- package/plugins/dsh/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/plugins/dsh/skills/domain-modeling/SKILL.md +74 -0
- package/plugins/dsh/skills/grill-with-docs/SKILL.md +7 -0
- package/plugins/dsh/skills/grilling/SKILL.md +12 -0
- package/plugins/dsh/skills/improve-codebase-architecture/SKILL.md +156 -0
- package/plugins/dsh/skills/ralph-loop/SKILL.md +198 -0
- package/plugins/dsh/skills/ralph-loop/references/components.md +113 -0
- package/plugins/dsh/skills/ralph-loop/references/phase-2-build-ralph.md +89 -0
- package/plugins/dsh/skills/ralph-loop/references/workflow-details.md +99 -0
- package/plugins/dsh/skills/ralph-loop/templates/progress-log.md +36 -0
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +103 -0
- package/plugins/dsh/skills/sprint-flow/SKILL.md +411 -0
- package/plugins/dsh/skills/sprint-flow/__tests__/sprint-flow.test.cjs +457 -0
- package/plugins/dsh/skills/sprint-flow/hooks.md +18 -0
- package/plugins/dsh/skills/sprint-flow/references/components/memory.md +87 -0
- package/plugins/dsh/skills/sprint-flow/references/components/middleware.md +75 -0
- package/plugins/dsh/skills/sprint-flow/references/components/skill-invocations.md +105 -0
- package/plugins/dsh/skills/sprint-flow/references/components/system-prompt.md +27 -0
- package/plugins/dsh/skills/sprint-flow/references/components/tool-descriptions.md +95 -0
- package/plugins/dsh/skills/sprint-flow/references/force-levels.md +204 -0
- package/plugins/dsh/skills/sprint-flow/references/orchestration-rules.md +236 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-1-prep.md +294 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-2-design.md +273 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-3-build.md +348 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-4-verify.md +225 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-5-ship.md +222 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-6-close.md +266 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-overview.md +486 -0
- package/plugins/dsh/skills/sprint-flow/scripts/verify-sprint-state.sh +38 -0
- package/plugins/dsh/skills/sprint-flow/templates/auto-estimate-learning-log.md +136 -0
- package/plugins/dsh/skills/sprint-flow/templates/auto-estimate-output-template.md +110 -0
- package/plugins/dsh/skills/sprint-flow/templates/emergent-issues-template.md +120 -0
- package/plugins/dsh/skills/sprint-flow/templates/pain-document-template.md +115 -0
- package/plugins/dsh/skills/sprint-flow/templates/sprint-progress-template.md +140 -0
- package/plugins/dsh/skills/sprint-flow/templates/sprint-summary-template.md +121 -0
- package/plugins/dsh/skills/sprint-flow/tools.md +29 -0
- package/plugins/dsh/skills/test-driven-development/SKILL.md +371 -0
- package/plugins/dsh/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +56 -0
- package/plugins/dsh/skills/test-specification-alignment/SKILL.md +205 -0
- package/plugins/dsh/skills/test-specification-alignment/references/alignment-verification-algorithm.md +493 -0
- package/plugins/dsh/skills/test-specification-alignment/references/phase2-constraint-enforcement.md +431 -0
- package/plugins/dsh/skills/test-specification-alignment/references/specification-format.md +348 -0
- package/plugins/dsh/skills/to-issues/SKILL.md +277 -0
- package/plugins/dsh/src/command.test.ts +102 -0
- package/plugins/dsh/src/command.ts +75 -0
- package/plugins/dsh/src/contract.test.ts +62 -0
- package/plugins/dsh/src/gate-runner.test.ts +100 -0
- package/plugins/dsh/src/gate-runner.ts +58 -0
- package/plugins/dsh/src/index.test.ts +97 -0
- package/plugins/dsh/src/index.ts +94 -0
- package/plugins/dsh/src/skills.test.ts +58 -0
- package/plugins/dsh/tsconfig.build.json +13 -0
- package/plugins/dsh/tsconfig.json +19 -0
- package/plugins/dsh/vitest.config.ts +8 -0
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/scripts/prepack.cjs +1 -0
- package/plugins/opencode/skills/clipboard-vision/SKILL.md +47 -0
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/opencode/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
- package/plugins/opencode/skills/clipboard-vision/describe-clipboard.sh +161 -0
- package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/opencode/skills/delphi-review/.delphi-config.json.example +24 -27
- package/plugins/opencode/skills/delphi-review/AGENTS.md +12 -11
- package/plugins/opencode/skills/delphi-review/INSTALL.md +15 -17
- package/plugins/opencode/skills/delphi-review/SKILL.md +30 -29
- package/plugins/opencode/skills/delphi-review/opencode.json.delphi.example +6 -10
- package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +33 -49
- package/plugins/opencode/skills/delphi-review/references/requirements.md +66 -54
- package/plugins/opencode/skills/delphi-review/references/round-templates.md +7 -7
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/opencode/skills/sprint-flow/SKILL.md +4 -4
- package/plugins/opencode/skills/sprint-flow/references/components/skill-invocations.md +2 -2
- package/plugins/opencode/skills/sprint-flow/references/force-levels.md +37 -35
- package/plugins/opencode/skills/sprint-flow/references/orchestration-rules.md +1 -1
- package/plugins/opencode/skills/sprint-flow/references/phase-1-prep.md +1 -1
- package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +24 -14
- package/plugins/opencode/skills/sprint-flow/references/phase-4-verify.md +6 -6
- package/plugins/opencode/skills/sprint-flow/references/phase-overview.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/clipboard-vision/SKILL.md +47 -0
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/SKILL.md +47 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/qoder/skills/delphi-review/.delphi-config.json.example +3 -11
- package/plugins/qoder/skills/delphi-review/AGENTS.md +14 -16
- package/plugins/qoder/skills/delphi-review/INSTALL.md +12 -12
- package/plugins/qoder/skills/delphi-review/SKILL.md +58 -25
- package/plugins/qoder/skills/delphi-review/opencode.json.delphi.example +1 -1
- package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +33 -49
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/qoder/skills/sprint-flow/SKILL.md +2 -2
- package/plugins/qoder/skills/sprint-flow/references/force-levels.md +38 -35
- package/plugins/qoder/skills/sprint-flow/references/phase-1-prep.md +1 -1
- package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +48 -6
- package/plugins/qoder/skills/sprint-flow/references/phase-4-verify.md +5 -6
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/scripts/__tests__/sync-package-content.test.js +30 -0
- package/scripts/delphi-external-review.cjs +194 -63
- package/scripts/sync-package-content.js +9 -2
- package/skills/clipboard-vision/SKILL.md +47 -0
- package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
- package/skills/clipboard-vision/describe-clipboard.sh +161 -0
- package/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/skills/delphi-review/.delphi-config.json.example +24 -27
- package/skills/delphi-review/AGENTS.md +12 -11
- package/skills/delphi-review/INSTALL.md +15 -17
- package/skills/delphi-review/SKILL.md +30 -29
- package/skills/delphi-review/opencode.json.delphi.example +6 -10
- package/skills/delphi-review/references/code-walkthrough.md +33 -49
- package/skills/delphi-review/references/requirements.md +66 -54
- package/skills/delphi-review/references/round-templates.md +7 -7
- package/skills/sprint-flow/AGENTS.md +4 -4
- package/skills/sprint-flow/SKILL.md +4 -4
- package/skills/sprint-flow/references/components/skill-invocations.md +2 -2
- package/skills/sprint-flow/references/force-levels.md +37 -35
- package/skills/sprint-flow/references/orchestration-rules.md +1 -1
- package/skills/sprint-flow/references/phase-1-prep.md +1 -1
- package/skills/sprint-flow/references/phase-2-design.md +24 -14
- package/skills/sprint-flow/references/phase-4-verify.md +6 -6
- package/skills/sprint-flow/references/phase-overview.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +3 -3
- package/sprint-gate.sh +291 -0
|
@@ -34,11 +34,15 @@ describe('MutmutRunner', () => {
|
|
|
34
34
|
|
|
35
35
|
beforeEach(async () => {
|
|
36
36
|
vi.clearAllMocks();
|
|
37
|
+
vi.mocked(execSync).mockReset();
|
|
38
|
+
vi.mocked(spawn).mockReset();
|
|
39
|
+
vi.mocked(execSync).mockReturnValue('mutmut, version 3.0.0\n');
|
|
37
40
|
tmpDir = join(tmpdir(), `xp-gate-mutmut-test-${Date.now()}`);
|
|
38
41
|
MutmutRunner = (await import('../runners/mutmut-runner')).MutmutRunner;
|
|
39
42
|
});
|
|
40
43
|
|
|
41
44
|
afterEach(() => {
|
|
45
|
+
vi.useRealTimers();
|
|
42
46
|
vi.restoreAllMocks();
|
|
43
47
|
});
|
|
44
48
|
|
|
@@ -78,6 +82,57 @@ describe('MutmutRunner', () => {
|
|
|
78
82
|
const result = await runner.isAvailable();
|
|
79
83
|
expect(result).toBe(false);
|
|
80
84
|
});
|
|
85
|
+
|
|
86
|
+
it('should use WSL when native mutmut is unavailable on Windows', async () => {
|
|
87
|
+
vi.mocked(execSync).mockImplementation((command) => {
|
|
88
|
+
if (command === 'mutmut --version') {
|
|
89
|
+
throw new Error('command not found: mutmut');
|
|
90
|
+
}
|
|
91
|
+
return 'mutmut, version 3.0.0\n';
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const runner = new MutmutRunner('win32');
|
|
95
|
+
const result = await runner.isAvailable();
|
|
96
|
+
|
|
97
|
+
expect(result).toBe(true);
|
|
98
|
+
expect(execSync).toHaveBeenNthCalledWith(2, 'wsl mutmut --version', {
|
|
99
|
+
stdio: 'pipe',
|
|
100
|
+
timeout: 5000,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should keep native detection unchanged on non-Windows platforms', async () => {
|
|
105
|
+
vi.mocked(execSync).mockReturnValue('mutmut, version 3.0.0\n');
|
|
106
|
+
|
|
107
|
+
const runner = new MutmutRunner('linux');
|
|
108
|
+
const result = await runner.isAvailable();
|
|
109
|
+
|
|
110
|
+
expect(result).toBe(true);
|
|
111
|
+
expect(execSync).toHaveBeenCalledTimes(1);
|
|
112
|
+
expect(execSync).toHaveBeenCalledWith('mutmut --version', {
|
|
113
|
+
stdio: 'pipe',
|
|
114
|
+
timeout: 5000,
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should report unavailable when native mutmut and WSL are unavailable', async () => {
|
|
119
|
+
vi.mocked(execSync).mockImplementation(() => {
|
|
120
|
+
throw new Error('command not found');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const runner = new MutmutRunner('win32');
|
|
124
|
+
const result = await runner.isAvailable();
|
|
125
|
+
|
|
126
|
+
expect(result).toBe(false);
|
|
127
|
+
const outcome = await runner.run({
|
|
128
|
+
files: ['src/main.py'],
|
|
129
|
+
timeoutMs: 60000,
|
|
130
|
+
cwd: tmpDir,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(outcome).toEqual({ report: null, timedOut: false });
|
|
134
|
+
expect(spawn).not.toHaveBeenCalled();
|
|
135
|
+
});
|
|
81
136
|
});
|
|
82
137
|
|
|
83
138
|
describe('run', () => {
|
|
@@ -131,7 +186,393 @@ describe('MutmutRunner', () => {
|
|
|
131
186
|
expect(result.report!.nrOfSurvivedMutants).toBe(29);
|
|
132
187
|
});
|
|
133
188
|
|
|
134
|
-
it('should
|
|
189
|
+
it('should execute native mutmut when native detection succeeds on Windows', async () => {
|
|
190
|
+
let closeCb: ((code: number | null) => void) | null = null;
|
|
191
|
+
const mockChild = {
|
|
192
|
+
stdout: { on: vi.fn() },
|
|
193
|
+
stderr: { on: vi.fn() },
|
|
194
|
+
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
195
|
+
if (event === 'close') closeCb = cb;
|
|
196
|
+
}),
|
|
197
|
+
kill: vi.fn(),
|
|
198
|
+
killed: false,
|
|
199
|
+
};
|
|
200
|
+
vi.mocked(execSync).mockImplementation((command) => {
|
|
201
|
+
if (command === 'mutmut --version') return 'mutmut, version 3.0.0\n';
|
|
202
|
+
throw new Error('WSL unavailable');
|
|
203
|
+
});
|
|
204
|
+
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
205
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
206
|
+
|
|
207
|
+
const runner = new MutmutRunner('win32');
|
|
208
|
+
expect(await runner.isAvailable()).toBe(true);
|
|
209
|
+
const promise = runner.run({
|
|
210
|
+
files: ['src/main.py'],
|
|
211
|
+
timeoutMs: 60000,
|
|
212
|
+
cwd: 'C:\\work\\project',
|
|
213
|
+
});
|
|
214
|
+
closeCb!(0);
|
|
215
|
+
await promise;
|
|
216
|
+
|
|
217
|
+
expect(execSync).toHaveBeenCalledTimes(1);
|
|
218
|
+
expect(spawn).toHaveBeenCalledWith(
|
|
219
|
+
'mutmut',
|
|
220
|
+
['run'],
|
|
221
|
+
expect.objectContaining({ cwd: 'C:\\work\\project' }),
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should execute selected WSL route with Linux --cd and Windows host cwd', async () => {
|
|
226
|
+
let closeCb: ((code: number | null) => void) | null = null;
|
|
227
|
+
const mockChild = {
|
|
228
|
+
stdout: { on: vi.fn() },
|
|
229
|
+
stderr: { on: vi.fn() },
|
|
230
|
+
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
231
|
+
if (event === 'close') closeCb = cb;
|
|
232
|
+
}),
|
|
233
|
+
kill: vi.fn(),
|
|
234
|
+
killed: false,
|
|
235
|
+
};
|
|
236
|
+
vi.mocked(execSync).mockImplementation((command) => {
|
|
237
|
+
if (command === 'mutmut --version') {
|
|
238
|
+
throw new Error('native mutmut unavailable');
|
|
239
|
+
}
|
|
240
|
+
return 'mutmut, version 3.0.0\n';
|
|
241
|
+
});
|
|
242
|
+
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
243
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
244
|
+
|
|
245
|
+
const runner = new MutmutRunner('win32');
|
|
246
|
+
expect(await runner.isAvailable()).toBe(true);
|
|
247
|
+
const promise = runner.run({
|
|
248
|
+
files: ['src/main.py'],
|
|
249
|
+
timeoutMs: 60000,
|
|
250
|
+
cwd: 'C:\\work\\project',
|
|
251
|
+
});
|
|
252
|
+
closeCb!(0);
|
|
253
|
+
await promise;
|
|
254
|
+
|
|
255
|
+
expect(spawn).toHaveBeenCalledWith(
|
|
256
|
+
'wsl',
|
|
257
|
+
['--cd', '/mnt/c/work/project', 'mutmut', 'run'],
|
|
258
|
+
expect.objectContaining({ cwd: 'C:\\work\\project' }),
|
|
259
|
+
);
|
|
260
|
+
expect(vi.mocked(spawn).mock.calls[0]?.[2]).not.toHaveProperty('shell');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should preserve spaced and metacharacter paths as one WSL argv item', async () => {
|
|
264
|
+
let closeCb: ((code: number | null) => void) | null = null;
|
|
265
|
+
const mockChild = {
|
|
266
|
+
stdout: { on: vi.fn() },
|
|
267
|
+
stderr: { on: vi.fn() },
|
|
268
|
+
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
269
|
+
if (event === 'close') closeCb = cb;
|
|
270
|
+
}),
|
|
271
|
+
kill: vi.fn(),
|
|
272
|
+
killed: false,
|
|
273
|
+
};
|
|
274
|
+
vi.mocked(execSync).mockImplementation((command) => {
|
|
275
|
+
if (command === 'mutmut --version') throw new Error('native unavailable');
|
|
276
|
+
return 'mutmut, version 3.0.0\n';
|
|
277
|
+
});
|
|
278
|
+
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
279
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
280
|
+
|
|
281
|
+
const runner = new MutmutRunner('win32');
|
|
282
|
+
expect(await runner.isAvailable()).toBe(true);
|
|
283
|
+
const promise = runner.run({
|
|
284
|
+
files: ['src/main.py'],
|
|
285
|
+
timeoutMs: 60000,
|
|
286
|
+
cwd: 'C:\\work dir\\safe; echo injected',
|
|
287
|
+
});
|
|
288
|
+
closeCb!(0);
|
|
289
|
+
await promise;
|
|
290
|
+
|
|
291
|
+
expect(spawn).toHaveBeenCalledWith(
|
|
292
|
+
'wsl',
|
|
293
|
+
['--cd', '/mnt/c/work dir/safe; echo injected', 'mutmut', 'run'],
|
|
294
|
+
expect.objectContaining({ cwd: 'C:\\work dir\\safe; echo injected' }),
|
|
295
|
+
);
|
|
296
|
+
expect(vi.mocked(spawn).mock.calls[0]?.[2]).not.toHaveProperty('shell');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it.each([
|
|
300
|
+
['uppercase drive', 'C:\\Work\\Project', '/mnt/c/Work/Project'],
|
|
301
|
+
['forward slash drive', 'D:/Work/Project', '/mnt/d/Work/Project'],
|
|
302
|
+
])('should convert %s paths for WSL', async (_name, windowsPath, linuxPath) => {
|
|
303
|
+
let closeCb: ((code: number | null) => void) | null = null;
|
|
304
|
+
const mockChild = {
|
|
305
|
+
stdout: { on: vi.fn() },
|
|
306
|
+
stderr: { on: vi.fn() },
|
|
307
|
+
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
308
|
+
if (event === 'close') closeCb = cb;
|
|
309
|
+
}),
|
|
310
|
+
kill: vi.fn(),
|
|
311
|
+
killed: false,
|
|
312
|
+
};
|
|
313
|
+
vi.mocked(execSync).mockImplementation((command) => {
|
|
314
|
+
if (command === 'mutmut --version') throw new Error('native unavailable');
|
|
315
|
+
return 'mutmut, version 3.0.0\n';
|
|
316
|
+
});
|
|
317
|
+
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
318
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
319
|
+
|
|
320
|
+
const runner = new MutmutRunner('win32');
|
|
321
|
+
expect(await runner.isAvailable()).toBe(true);
|
|
322
|
+
const promise = runner.run({ files: ['src/main.py'], timeoutMs: 60000, cwd: windowsPath });
|
|
323
|
+
closeCb!(0);
|
|
324
|
+
await promise;
|
|
325
|
+
|
|
326
|
+
expect(vi.mocked(spawn).mock.calls[0]?.[1]).toEqual(['--cd', linuxPath, 'mutmut', 'run']);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it.each(['relative/project', '\\\\server\\share\\project'])(
|
|
330
|
+
'should fail closed for non-drive WSL path %s', async (cwd) => {
|
|
331
|
+
vi.mocked(execSync).mockImplementation((command) => {
|
|
332
|
+
if (command === 'mutmut --version') throw new Error('native unavailable');
|
|
333
|
+
return 'mutmut, version 3.0.0\n';
|
|
334
|
+
});
|
|
335
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
336
|
+
|
|
337
|
+
const runner = new MutmutRunner('win32');
|
|
338
|
+
expect(await runner.isAvailable()).toBe(true);
|
|
339
|
+
const outcome = await runner.run({ files: ['src/main.py'], timeoutMs: 60000, cwd });
|
|
340
|
+
|
|
341
|
+
expect(outcome).toEqual({ report: null, timedOut: false });
|
|
342
|
+
expect(spawn).not.toHaveBeenCalled();
|
|
343
|
+
},
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
it('should follow repeated native to WSL availability transitions', async () => {
|
|
347
|
+
let closeCb: ((code: number | null) => void) | null = null;
|
|
348
|
+
const mockChild = {
|
|
349
|
+
stdout: { on: vi.fn() },
|
|
350
|
+
stderr: { on: vi.fn() },
|
|
351
|
+
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
352
|
+
if (event === 'close') closeCb = cb;
|
|
353
|
+
}),
|
|
354
|
+
kill: vi.fn(),
|
|
355
|
+
killed: false,
|
|
356
|
+
};
|
|
357
|
+
let nativeAvailable = true;
|
|
358
|
+
vi.mocked(execSync).mockImplementation((command) => {
|
|
359
|
+
if (command === 'mutmut --version' && nativeAvailable) return 'native';
|
|
360
|
+
if (command === 'wsl mutmut --version') return 'wsl';
|
|
361
|
+
throw new Error('unavailable');
|
|
362
|
+
});
|
|
363
|
+
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
364
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
365
|
+
|
|
366
|
+
const runner = new MutmutRunner('win32');
|
|
367
|
+
expect(await runner.isAvailable()).toBe(true);
|
|
368
|
+
nativeAvailable = false;
|
|
369
|
+
expect(await runner.isAvailable()).toBe(true);
|
|
370
|
+
const promise = runner.run({ files: ['src/main.py'], timeoutMs: 60000, cwd: 'C:\\repo' });
|
|
371
|
+
closeCb!(0);
|
|
372
|
+
await promise;
|
|
373
|
+
|
|
374
|
+
expect(vi.mocked(spawn).mock.calls[0]?.[0]).toBe('wsl');
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('should follow repeated WSL to native availability transitions', async () => {
|
|
378
|
+
const mockChild = {
|
|
379
|
+
stdout: { on: vi.fn() },
|
|
380
|
+
stderr: { on: vi.fn() },
|
|
381
|
+
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
382
|
+
if (event === 'close') cb(0);
|
|
383
|
+
}),
|
|
384
|
+
kill: vi.fn(),
|
|
385
|
+
killed: false,
|
|
386
|
+
};
|
|
387
|
+
let nativeAvailable = false;
|
|
388
|
+
vi.mocked(execSync).mockImplementation((command) => {
|
|
389
|
+
if (command === 'mutmut --version' && nativeAvailable) return 'native';
|
|
390
|
+
if (command === 'wsl mutmut --version') return 'wsl';
|
|
391
|
+
throw new Error('unavailable');
|
|
392
|
+
});
|
|
393
|
+
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
394
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
395
|
+
|
|
396
|
+
const runner = new MutmutRunner('win32');
|
|
397
|
+
expect(await runner.isAvailable()).toBe(true);
|
|
398
|
+
nativeAvailable = true;
|
|
399
|
+
expect(await runner.isAvailable()).toBe(true);
|
|
400
|
+
await runner.run({ files: ['src/main.py'], timeoutMs: 60000, cwd: 'C:\\repo' });
|
|
401
|
+
|
|
402
|
+
expect(vi.mocked(spawn).mock.calls[0]?.[0]).toBe('mutmut');
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('should stop spawning after availability transitions to unavailable', async () => {
|
|
406
|
+
vi.mocked(execSync).mockImplementation(() => {
|
|
407
|
+
throw new Error('unavailable');
|
|
408
|
+
});
|
|
409
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
410
|
+
|
|
411
|
+
const runner = new MutmutRunner('win32');
|
|
412
|
+
expect(await runner.isAvailable()).toBe(false);
|
|
413
|
+
const outcome = await runner.run({ files: ['src/main.py'], timeoutMs: 60000, cwd: 'C:\\repo' });
|
|
414
|
+
|
|
415
|
+
expect(outcome).toEqual({ report: null, timedOut: false });
|
|
416
|
+
expect(spawn).not.toHaveBeenCalled();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('should clear timers when the child closes before timeout', async () => {
|
|
420
|
+
vi.useFakeTimers();
|
|
421
|
+
const mockChild = {
|
|
422
|
+
stdout: { on: vi.fn() },
|
|
423
|
+
stderr: { on: vi.fn() },
|
|
424
|
+
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
425
|
+
if (event === 'close') cb(0);
|
|
426
|
+
}),
|
|
427
|
+
kill: vi.fn(),
|
|
428
|
+
killed: false,
|
|
429
|
+
};
|
|
430
|
+
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
431
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
432
|
+
|
|
433
|
+
const result = await new MutmutRunner('linux').run({
|
|
434
|
+
files: ['src/main.py'],
|
|
435
|
+
timeoutMs: 100,
|
|
436
|
+
cwd: tmpDir,
|
|
437
|
+
});
|
|
438
|
+
await vi.advanceTimersByTimeAsync(5100);
|
|
439
|
+
|
|
440
|
+
expect(result.timedOut).toBe(false);
|
|
441
|
+
expect(mockChild.kill).not.toHaveBeenCalled();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('should clear timers when the child emits an error', async () => {
|
|
445
|
+
vi.useFakeTimers();
|
|
446
|
+
const mockChild = {
|
|
447
|
+
stdout: { on: vi.fn() },
|
|
448
|
+
stderr: { on: vi.fn() },
|
|
449
|
+
on: vi.fn((event: string, cb: (error: Error) => void) => {
|
|
450
|
+
if (event === 'error') cb(new Error('spawn failed'));
|
|
451
|
+
}),
|
|
452
|
+
kill: vi.fn(),
|
|
453
|
+
killed: false,
|
|
454
|
+
};
|
|
455
|
+
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
456
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
457
|
+
|
|
458
|
+
const result = await new MutmutRunner('linux').run({
|
|
459
|
+
files: ['src/main.py'],
|
|
460
|
+
timeoutMs: 100,
|
|
461
|
+
cwd: tmpDir,
|
|
462
|
+
});
|
|
463
|
+
await vi.advanceTimersByTimeAsync(5100);
|
|
464
|
+
|
|
465
|
+
expect(result.error).toBe('spawn failed');
|
|
466
|
+
expect(mockChild.kill).not.toHaveBeenCalled();
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('should report timeout when the timed-out process closes with a code', async () => {
|
|
470
|
+
vi.useFakeTimers();
|
|
471
|
+
let closeCb: ((code: number | null) => void) | null = null;
|
|
472
|
+
const mockChild = {
|
|
473
|
+
stdout: { on: vi.fn() },
|
|
474
|
+
stderr: { on: vi.fn() },
|
|
475
|
+
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
476
|
+
if (event === 'close') closeCb = cb;
|
|
477
|
+
}),
|
|
478
|
+
kill: vi.fn(),
|
|
479
|
+
killed: false,
|
|
480
|
+
};
|
|
481
|
+
vi.mocked(execSync).mockImplementation((command) => {
|
|
482
|
+
if (command === 'mutmut --version') throw new Error('native unavailable');
|
|
483
|
+
return 'wsl';
|
|
484
|
+
});
|
|
485
|
+
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
486
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
487
|
+
|
|
488
|
+
const runner = new MutmutRunner('win32');
|
|
489
|
+
expect(await runner.isAvailable()).toBe(true);
|
|
490
|
+
const promise = runner.run({ files: ['src/main.py'], timeoutMs: 100, cwd: 'C:\\repo' });
|
|
491
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
492
|
+
closeCb!(1);
|
|
493
|
+
const outcome = await promise;
|
|
494
|
+
|
|
495
|
+
expect(outcome.timedOut).toBe(true);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('should escalate to SIGKILL when SIGTERM does not close the child', async () => {
|
|
499
|
+
vi.useFakeTimers();
|
|
500
|
+
let closeCb: ((code: number | null) => void) | null = null;
|
|
501
|
+
let killCount = 0;
|
|
502
|
+
const mockChild = {
|
|
503
|
+
stdout: { on: vi.fn() },
|
|
504
|
+
stderr: { on: vi.fn() },
|
|
505
|
+
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
506
|
+
if (event === 'close') closeCb = cb;
|
|
507
|
+
}),
|
|
508
|
+
kill: vi.fn(() => {
|
|
509
|
+
killCount += 1;
|
|
510
|
+
if (killCount === 2) closeCb?.(null);
|
|
511
|
+
return true;
|
|
512
|
+
}),
|
|
513
|
+
killed: false,
|
|
514
|
+
};
|
|
515
|
+
vi.mocked(execSync).mockImplementation((command) => {
|
|
516
|
+
if (command === 'mutmut --version') throw new Error('native unavailable');
|
|
517
|
+
return 'wsl';
|
|
518
|
+
});
|
|
519
|
+
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
520
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
521
|
+
|
|
522
|
+
const runner = new MutmutRunner('win32');
|
|
523
|
+
expect(await runner.isAvailable()).toBe(true);
|
|
524
|
+
const promise = runner.run({ files: ['src/main.py'], timeoutMs: 100, cwd: 'C:\\repo' });
|
|
525
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
526
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
527
|
+
|
|
528
|
+
expect(mockChild.kill).toHaveBeenNthCalledWith(1, 'SIGTERM');
|
|
529
|
+
expect(mockChild.kill).toHaveBeenNthCalledWith(2, 'SIGKILL');
|
|
530
|
+
await promise;
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('should kill the selected WSL process and report timeout', async () => {
|
|
534
|
+
vi.useFakeTimers();
|
|
535
|
+
let closeCb: ((code: number | null) => void) | null = null;
|
|
536
|
+
const mockChild = {
|
|
537
|
+
stdout: { on: vi.fn() },
|
|
538
|
+
stderr: { on: vi.fn() },
|
|
539
|
+
on: vi.fn((event: string, cb: (code: number | null) => void) => {
|
|
540
|
+
if (event === 'close') closeCb = cb;
|
|
541
|
+
}),
|
|
542
|
+
kill: vi.fn(() => {
|
|
543
|
+
closeCb?.(null);
|
|
544
|
+
return true;
|
|
545
|
+
}),
|
|
546
|
+
killed: false,
|
|
547
|
+
};
|
|
548
|
+
vi.mocked(execSync).mockImplementation((command) => {
|
|
549
|
+
if (command === 'mutmut --version') throw new Error('native unavailable');
|
|
550
|
+
return 'mutmut, version 3.0.0\n';
|
|
551
|
+
});
|
|
552
|
+
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
|
|
553
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
554
|
+
|
|
555
|
+
const runner = new MutmutRunner('win32');
|
|
556
|
+
expect(await runner.isAvailable()).toBe(true);
|
|
557
|
+
const promise = runner.run({
|
|
558
|
+
files: ['src/main.py'],
|
|
559
|
+
timeoutMs: 100,
|
|
560
|
+
cwd: 'D:\\repo',
|
|
561
|
+
});
|
|
562
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
563
|
+
const result = await promise;
|
|
564
|
+
|
|
565
|
+
expect(spawn).toHaveBeenCalledWith(
|
|
566
|
+
'wsl',
|
|
567
|
+
['--cd', '/mnt/d/repo', 'mutmut', 'run'],
|
|
568
|
+
expect.objectContaining({ cwd: 'D:\\repo' }),
|
|
569
|
+
);
|
|
570
|
+
expect(mockChild.kill).toHaveBeenCalledWith('SIGTERM');
|
|
571
|
+
expect(result).toEqual({ report: null, timedOut: true });
|
|
572
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('should return timedOut=false when process closes before timeout (code=null)', async () => {
|
|
135
576
|
const mockChild = {
|
|
136
577
|
stdout: { on: vi.fn() },
|
|
137
578
|
stderr: { on: vi.fn() },
|
|
@@ -151,7 +592,7 @@ describe('MutmutRunner', () => {
|
|
|
151
592
|
cwd: tmpDir,
|
|
152
593
|
});
|
|
153
594
|
|
|
154
|
-
expect(result.timedOut).toBe(
|
|
595
|
+
expect(result.timedOut).toBe(false);
|
|
155
596
|
expect(result.report).toBeNull();
|
|
156
597
|
});
|
|
157
598
|
|
|
@@ -41,9 +41,7 @@ describe('update-baseline.ts - Mutation Baseline Update CLI', () => {
|
|
|
41
41
|
let originalArgv: string[];
|
|
42
42
|
let originalExit: typeof process.exit;
|
|
43
43
|
let originalCwd: typeof process.cwd;
|
|
44
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
44
|
let consoleLogSpy: any;
|
|
46
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
45
|
let consoleErrorSpy: any;
|
|
48
46
|
let exitCode: number | undefined;
|
|
49
47
|
|
|
@@ -9,6 +9,9 @@ import type {
|
|
|
9
9
|
MutationRunResult,
|
|
10
10
|
} from './types';
|
|
11
11
|
|
|
12
|
+
type Platform = NodeJS.Platform;
|
|
13
|
+
type MutmutRoute = 'native' | 'wsl' | 'unavailable' | 'unresolved';
|
|
14
|
+
|
|
12
15
|
/**
|
|
13
16
|
* mutmut mutation runner for Python files.
|
|
14
17
|
*
|
|
@@ -25,16 +28,40 @@ export class MutmutRunner implements MutationRunner {
|
|
|
25
28
|
readonly name = 'mutmut';
|
|
26
29
|
readonly extensions = ['py'];
|
|
27
30
|
|
|
31
|
+
private route: MutmutRoute = 'unresolved';
|
|
32
|
+
|
|
33
|
+
constructor(private readonly platform: Platform = process.platform) {}
|
|
34
|
+
|
|
28
35
|
async isAvailable(): Promise<boolean> {
|
|
29
36
|
try {
|
|
30
37
|
execSync('mutmut --version', { stdio: 'pipe', timeout: 5000 });
|
|
38
|
+
this.route = 'native';
|
|
31
39
|
return true;
|
|
32
40
|
} catch {
|
|
33
|
-
|
|
41
|
+
if (this.platform !== 'win32') {
|
|
42
|
+
this.route = 'unavailable';
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
execSync('wsl mutmut --version', { stdio: 'pipe', timeout: 5000 });
|
|
48
|
+
this.route = 'wsl';
|
|
49
|
+
return true;
|
|
50
|
+
} catch {
|
|
51
|
+
this.route = 'unavailable';
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
34
54
|
}
|
|
35
55
|
}
|
|
36
56
|
|
|
37
57
|
async run(options: RunMutationOptions): Promise<MutationRunOutcome> {
|
|
58
|
+
if (this.route === 'unresolved' && this.platform === 'win32') {
|
|
59
|
+
await this.isAvailable();
|
|
60
|
+
}
|
|
61
|
+
if (this.route === 'unavailable') {
|
|
62
|
+
return { report: null, timedOut: false };
|
|
63
|
+
}
|
|
64
|
+
|
|
38
65
|
// Extract unique directories from file list
|
|
39
66
|
const sourceDirs = this.extractUniqueDirs(options.files);
|
|
40
67
|
|
|
@@ -68,14 +95,36 @@ export class MutmutRunner implements MutationRunner {
|
|
|
68
95
|
|
|
69
96
|
private async runMutmut(options: RunMutationOptions): Promise<MutationRunOutcome> {
|
|
70
97
|
return new Promise((resolve) => {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
98
|
+
const command = this.route === 'wsl' ? 'wsl' : 'mutmut';
|
|
99
|
+
const wslPath = this.route === 'wsl' ? this.toWslPath(options.cwd) : options.cwd;
|
|
100
|
+
if (wslPath === null) {
|
|
101
|
+
resolve({ report: null, timedOut: false });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const args = this.route === 'wsl' ? ['--cd', wslPath, 'mutmut', 'run'] : ['run'];
|
|
105
|
+
const child = spawn(command, args, { stdio: 'pipe', cwd: options.cwd });
|
|
76
106
|
|
|
77
107
|
let stderr = '';
|
|
78
108
|
let stdout = '';
|
|
109
|
+
let didTimeOut = false;
|
|
110
|
+
let settled = false;
|
|
111
|
+
const timeoutId = setTimeout(() => {
|
|
112
|
+
didTimeOut = true;
|
|
113
|
+
child.kill('SIGTERM');
|
|
114
|
+
if (settled) return;
|
|
115
|
+
escalationId = setTimeout(() => {
|
|
116
|
+
if (!settled) child.kill('SIGKILL');
|
|
117
|
+
}, 5000);
|
|
118
|
+
}, options.timeoutMs);
|
|
119
|
+
let escalationId: NodeJS.Timeout | undefined;
|
|
120
|
+
|
|
121
|
+
const settle = (outcome: MutationRunOutcome): void => {
|
|
122
|
+
if (settled) return;
|
|
123
|
+
settled = true;
|
|
124
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
125
|
+
if (escalationId) clearTimeout(escalationId);
|
|
126
|
+
resolve(outcome);
|
|
127
|
+
};
|
|
79
128
|
|
|
80
129
|
child.stdout?.on('data', (data: Buffer) => {
|
|
81
130
|
stdout += data.toString();
|
|
@@ -85,37 +134,38 @@ export class MutmutRunner implements MutationRunner {
|
|
|
85
134
|
stderr += data.toString();
|
|
86
135
|
});
|
|
87
136
|
|
|
88
|
-
const timeoutId = setTimeout(() => {
|
|
89
|
-
child.kill('SIGTERM');
|
|
90
|
-
setTimeout(() => {
|
|
91
|
-
if (!child.killed) child.kill('SIGKILL');
|
|
92
|
-
}, 5000);
|
|
93
|
-
}, options.timeoutMs);
|
|
94
|
-
|
|
95
137
|
child.on('close', (code) => {
|
|
96
138
|
clearTimeout(timeoutId);
|
|
97
139
|
|
|
98
140
|
if (code === null) {
|
|
99
|
-
|
|
141
|
+
settle({ report: null, timedOut: didTimeOut });
|
|
100
142
|
return;
|
|
101
143
|
}
|
|
102
144
|
|
|
103
145
|
// Parse results from mutmut run stdout (v3.x emoji progress)
|
|
104
146
|
const report = this.parseEmojiProgress(stdout);
|
|
105
|
-
|
|
147
|
+
settle({
|
|
106
148
|
report,
|
|
107
|
-
timedOut:
|
|
149
|
+
timedOut: didTimeOut,
|
|
108
150
|
error: code !== 0 ? stderr || stdout : undefined,
|
|
109
151
|
});
|
|
110
152
|
});
|
|
111
153
|
|
|
112
154
|
child.on('error', (err) => {
|
|
113
155
|
clearTimeout(timeoutId);
|
|
114
|
-
|
|
156
|
+
settle({ report: null, timedOut: didTimeOut, error: err.message });
|
|
115
157
|
});
|
|
116
158
|
});
|
|
117
159
|
}
|
|
118
160
|
|
|
161
|
+
private toWslPath(path: string): string | null {
|
|
162
|
+
const match = path.match(/^([A-Za-z]):[\\/](.*)$/);
|
|
163
|
+
if (!match) return null;
|
|
164
|
+
const drive = match[1]?.toLowerCase();
|
|
165
|
+
const remainder = match[2]?.replaceAll('\\', '/');
|
|
166
|
+
return `/mnt/${drive}/${remainder}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
119
169
|
/**
|
|
120
170
|
* Parse v3.x emoji progress line from mutmut run stdout.
|
|
121
171
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boyingliu01/xp-gate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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"
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
"gate-4.sh",
|
|
23
23
|
"gate-7.sh",
|
|
24
24
|
"gate-8.sh",
|
|
25
|
-
"gate-9.sh"
|
|
25
|
+
"gate-9.sh",
|
|
26
|
+
"gate-12-file-hygiene.sh",
|
|
27
|
+
"sprint-gate.sh"
|
|
26
28
|
],
|
|
27
29
|
"exports": {
|
|
28
30
|
"./tui": "./plugins/opencode/tui-plugin.ts"
|
|
@@ -52,5 +54,9 @@
|
|
|
52
54
|
"ai-development"
|
|
53
55
|
],
|
|
54
56
|
"author": "boyingliu01",
|
|
55
|
-
"license": "MIT"
|
|
57
|
+
"license": "MIT",
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"js-yaml": "^5.2.2",
|
|
60
|
+
"zod": "^4.4.3"
|
|
61
|
+
}
|
|
56
62
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xp-gate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 10 quality gates (Gate 0-9), Sprint Flow (11 phases), and Delphi multi-expert review (>=90% consensus).",
|
|
6
6
|
"author": {
|