@boyingliu01/xp-gate 0.18.5 → 0.19.0

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.
Files changed (254) hide show
  1. package/adapter-common.sh +27 -0
  2. package/adapters/gate-3.sh +0 -0
  3. package/adapters/gate-4.sh +0 -0
  4. package/adapters/gate-7.sh +0 -0
  5. package/adapters/gate-8.sh +0 -0
  6. package/adapters/gate-9.sh +0 -0
  7. package/adapters/powershell.sh +85 -33
  8. package/adapters/typescript.sh +8 -8
  9. package/build-integrity/__tests__/import-resolver.test.ts +22 -0
  10. package/build-integrity/__tests__/orchestrator.test.ts +1 -2
  11. package/build-integrity/gate-10.ts +7 -0
  12. package/gate-12-file-hygiene.sh +230 -0
  13. package/hooks/adapter-common.sh +27 -0
  14. package/hooks/gate-10.sh +105 -0
  15. package/hooks/gate-12-file-hygiene.sh +0 -0
  16. package/hooks/lib/validate-code-walkthrough.cjs +125 -0
  17. package/hooks/pre-commit +130 -33
  18. package/hooks/pre-push +15 -91
  19. package/lib/__tests__/check-version.test.js +67 -46
  20. package/lib/__tests__/doctor.test.js +7 -6
  21. package/lib/__tests__/gate-runner.test.js +267 -0
  22. package/lib/__tests__/init.test.js +32 -0
  23. package/lib/__tests__/npm-package.test.js +13 -0
  24. package/lib/__tests__/phase-transition.test.js +604 -12
  25. package/lib/__tests__/sprint-discovery.test.ts +0 -1
  26. package/lib/__tests__/sprint-status-rework.test.js +100 -1
  27. package/lib/__tests__/update-hooks.test.js +143 -10
  28. package/lib/__tests__/upgrade-exec.test.js +73 -45
  29. package/lib/__tests__/upgrade-skill-sync.test.js +1 -1
  30. package/lib/__tests__/upgrade.test.js +12 -11
  31. package/lib/doctor.js +2 -1
  32. package/lib/gate-runner.js +68 -23
  33. package/lib/init.js +34 -5
  34. package/lib/install-skill.js +1 -0
  35. package/lib/mattpocock-license-verdict.json +1 -1
  36. package/lib/phase-transition.js +203 -23
  37. package/lib/shared-paths.js +2 -0
  38. package/lib/sprint-status.js +15 -4
  39. package/lib/test-alignment.ts +3 -13
  40. package/lib/update-hooks.js +129 -15
  41. package/mock-policy/AGENTS.md +5 -5
  42. package/mock-policy/__tests__/gate-m3.test.ts +0 -5
  43. package/mock-policy/__tests__/scope-scanner.test.ts +8 -2
  44. package/mutation/AGENTS.md +4 -4
  45. package/mutation/__tests__/mutmut-runner.test.ts +443 -2
  46. package/mutation/__tests__/update-baseline.test.ts +0 -2
  47. package/mutation/runners/mutmut-runner.ts +67 -17
  48. package/package.json +9 -3
  49. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  50. package/plugins/claude-code/skills/clipboard-vision/SKILL.md +47 -0
  51. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  52. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  53. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  54. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  55. package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  56. package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.sh +161 -0
  57. package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  58. package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  59. package/plugins/claude-code/skills/delphi-review/.delphi-config.json.example +24 -27
  60. package/plugins/claude-code/skills/delphi-review/AGENTS.md +13 -12
  61. package/plugins/claude-code/skills/delphi-review/INSTALL.md +15 -17
  62. package/plugins/claude-code/skills/delphi-review/SKILL.md +30 -29
  63. package/plugins/claude-code/skills/delphi-review/opencode.json.delphi.example +6 -10
  64. package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +33 -49
  65. package/plugins/claude-code/skills/delphi-review/references/requirements.md +66 -54
  66. package/plugins/claude-code/skills/delphi-review/references/round-templates.md +7 -7
  67. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +5 -5
  68. package/plugins/claude-code/skills/sprint-flow/SKILL.md +4 -4
  69. package/plugins/claude-code/skills/sprint-flow/references/components/skill-invocations.md +2 -2
  70. package/plugins/claude-code/skills/sprint-flow/references/force-levels.md +37 -35
  71. package/plugins/claude-code/skills/sprint-flow/references/orchestration-rules.md +1 -1
  72. package/plugins/claude-code/skills/sprint-flow/references/phase-1-prep.md +1 -1
  73. package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +24 -14
  74. package/plugins/claude-code/skills/sprint-flow/references/phase-4-verify.md +6 -6
  75. package/plugins/claude-code/skills/sprint-flow/references/phase-overview.md +2 -2
  76. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
  77. package/plugins/dsh/README.md +25 -0
  78. package/plugins/dsh/cordis.patch.yml +7 -0
  79. package/plugins/dsh/package.json +70 -0
  80. package/plugins/dsh/scripts/prepack.cjs +82 -0
  81. package/plugins/dsh/skills/admin-template-guidelines/SKILL.md +113 -0
  82. package/plugins/dsh/skills/admin-template-guidelines/references/rule-1.md +91 -0
  83. package/plugins/dsh/skills/admin-template-guidelines/references/rule-2.md +155 -0
  84. package/plugins/dsh/skills/admin-template-guidelines/references/rule-3.md +70 -0
  85. package/plugins/dsh/skills/admin-template-guidelines/references/rule-4.md +154 -0
  86. package/plugins/dsh/skills/admin-template-guidelines/references/rule-5.md +126 -0
  87. package/plugins/dsh/skills/admin-template-guidelines/references/rule-6.md +180 -0
  88. package/plugins/dsh/skills/batch-grill-me/SKILL.md +15 -0
  89. package/plugins/dsh/skills/clipboard-vision/SKILL.md +47 -0
  90. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  91. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  92. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  93. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  94. package/plugins/dsh/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  95. package/plugins/dsh/skills/clipboard-vision/describe-clipboard.sh +161 -0
  96. package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  97. package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  98. package/plugins/dsh/skills/delphi-review/.delphi-config.json.example +70 -0
  99. package/plugins/dsh/skills/delphi-review/AGENTS.md +67 -0
  100. package/plugins/dsh/skills/delphi-review/INSTALL.md +150 -0
  101. package/plugins/dsh/skills/delphi-review/SKILL.md +559 -0
  102. package/plugins/dsh/skills/delphi-review/hooks.md +19 -0
  103. package/plugins/dsh/skills/delphi-review/opencode.json.delphi.example +52 -0
  104. package/plugins/dsh/skills/delphi-review/references/code-walkthrough.md +470 -0
  105. package/plugins/dsh/skills/delphi-review/references/orchestrator-dispatch.md +64 -0
  106. package/plugins/dsh/skills/delphi-review/references/requirements.md +402 -0
  107. package/plugins/dsh/skills/delphi-review/references/round-templates.md +134 -0
  108. package/plugins/dsh/skills/delphi-review/scripts/verify-consensus.sh +43 -0
  109. package/plugins/dsh/skills/delphi-review/tools.md +29 -0
  110. package/plugins/dsh/skills/domain-modeling/ADR-FORMAT.md +64 -0
  111. package/plugins/dsh/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  112. package/plugins/dsh/skills/domain-modeling/SKILL.md +74 -0
  113. package/plugins/dsh/skills/grill-with-docs/SKILL.md +7 -0
  114. package/plugins/dsh/skills/grilling/SKILL.md +12 -0
  115. package/plugins/dsh/skills/improve-codebase-architecture/SKILL.md +156 -0
  116. package/plugins/dsh/skills/ralph-loop/SKILL.md +198 -0
  117. package/plugins/dsh/skills/ralph-loop/references/components.md +113 -0
  118. package/plugins/dsh/skills/ralph-loop/references/phase-2-build-ralph.md +89 -0
  119. package/plugins/dsh/skills/ralph-loop/references/workflow-details.md +99 -0
  120. package/plugins/dsh/skills/ralph-loop/templates/progress-log.md +36 -0
  121. package/plugins/dsh/skills/sprint-flow/AGENTS.md +103 -0
  122. package/plugins/dsh/skills/sprint-flow/SKILL.md +411 -0
  123. package/plugins/dsh/skills/sprint-flow/__tests__/sprint-flow.test.cjs +457 -0
  124. package/plugins/dsh/skills/sprint-flow/hooks.md +18 -0
  125. package/plugins/dsh/skills/sprint-flow/references/components/memory.md +87 -0
  126. package/plugins/dsh/skills/sprint-flow/references/components/middleware.md +75 -0
  127. package/plugins/dsh/skills/sprint-flow/references/components/skill-invocations.md +105 -0
  128. package/plugins/dsh/skills/sprint-flow/references/components/system-prompt.md +27 -0
  129. package/plugins/dsh/skills/sprint-flow/references/components/tool-descriptions.md +95 -0
  130. package/plugins/dsh/skills/sprint-flow/references/force-levels.md +204 -0
  131. package/plugins/dsh/skills/sprint-flow/references/orchestration-rules.md +236 -0
  132. package/plugins/dsh/skills/sprint-flow/references/phase-1-prep.md +294 -0
  133. package/plugins/dsh/skills/sprint-flow/references/phase-2-design.md +273 -0
  134. package/plugins/dsh/skills/sprint-flow/references/phase-3-build.md +348 -0
  135. package/plugins/dsh/skills/sprint-flow/references/phase-4-verify.md +225 -0
  136. package/plugins/dsh/skills/sprint-flow/references/phase-5-ship.md +222 -0
  137. package/plugins/dsh/skills/sprint-flow/references/phase-6-close.md +266 -0
  138. package/plugins/dsh/skills/sprint-flow/references/phase-overview.md +486 -0
  139. package/plugins/dsh/skills/sprint-flow/scripts/verify-sprint-state.sh +38 -0
  140. package/plugins/dsh/skills/sprint-flow/templates/auto-estimate-learning-log.md +136 -0
  141. package/plugins/dsh/skills/sprint-flow/templates/auto-estimate-output-template.md +110 -0
  142. package/plugins/dsh/skills/sprint-flow/templates/emergent-issues-template.md +120 -0
  143. package/plugins/dsh/skills/sprint-flow/templates/pain-document-template.md +115 -0
  144. package/plugins/dsh/skills/sprint-flow/templates/sprint-progress-template.md +140 -0
  145. package/plugins/dsh/skills/sprint-flow/templates/sprint-summary-template.md +121 -0
  146. package/plugins/dsh/skills/sprint-flow/tools.md +29 -0
  147. package/plugins/dsh/skills/test-driven-development/SKILL.md +371 -0
  148. package/plugins/dsh/skills/test-driven-development/testing-anti-patterns.md +299 -0
  149. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +56 -0
  150. package/plugins/dsh/skills/test-specification-alignment/SKILL.md +205 -0
  151. package/plugins/dsh/skills/test-specification-alignment/references/alignment-verification-algorithm.md +493 -0
  152. package/plugins/dsh/skills/test-specification-alignment/references/phase2-constraint-enforcement.md +431 -0
  153. package/plugins/dsh/skills/test-specification-alignment/references/specification-format.md +348 -0
  154. package/plugins/dsh/skills/to-issues/SKILL.md +277 -0
  155. package/plugins/dsh/src/command.test.ts +102 -0
  156. package/plugins/dsh/src/command.ts +75 -0
  157. package/plugins/dsh/src/contract.test.ts +62 -0
  158. package/plugins/dsh/src/gate-runner.test.ts +100 -0
  159. package/plugins/dsh/src/gate-runner.ts +58 -0
  160. package/plugins/dsh/src/index.test.ts +97 -0
  161. package/plugins/dsh/src/index.ts +94 -0
  162. package/plugins/dsh/src/skills.test.ts +58 -0
  163. package/plugins/dsh/tsconfig.build.json +13 -0
  164. package/plugins/dsh/tsconfig.json +19 -0
  165. package/plugins/dsh/vitest.config.ts +8 -0
  166. package/plugins/opencode/package.json +1 -1
  167. package/plugins/opencode/scripts/prepack.cjs +1 -0
  168. package/plugins/opencode/skills/clipboard-vision/SKILL.md +47 -0
  169. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  170. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  171. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  172. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  173. package/plugins/opencode/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  174. package/plugins/opencode/skills/clipboard-vision/describe-clipboard.sh +161 -0
  175. package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  176. package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  177. package/plugins/opencode/skills/delphi-review/.delphi-config.json.example +24 -27
  178. package/plugins/opencode/skills/delphi-review/AGENTS.md +13 -12
  179. package/plugins/opencode/skills/delphi-review/INSTALL.md +15 -17
  180. package/plugins/opencode/skills/delphi-review/SKILL.md +30 -29
  181. package/plugins/opencode/skills/delphi-review/opencode.json.delphi.example +6 -10
  182. package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +33 -49
  183. package/plugins/opencode/skills/delphi-review/references/requirements.md +66 -54
  184. package/plugins/opencode/skills/delphi-review/references/round-templates.md +7 -7
  185. package/plugins/opencode/skills/sprint-flow/AGENTS.md +5 -5
  186. package/plugins/opencode/skills/sprint-flow/SKILL.md +4 -4
  187. package/plugins/opencode/skills/sprint-flow/references/components/skill-invocations.md +2 -2
  188. package/plugins/opencode/skills/sprint-flow/references/force-levels.md +37 -35
  189. package/plugins/opencode/skills/sprint-flow/references/orchestration-rules.md +1 -1
  190. package/plugins/opencode/skills/sprint-flow/references/phase-1-prep.md +1 -1
  191. package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +24 -14
  192. package/plugins/opencode/skills/sprint-flow/references/phase-4-verify.md +6 -6
  193. package/plugins/opencode/skills/sprint-flow/references/phase-overview.md +2 -2
  194. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
  195. package/plugins/qoder/plugin.json +1 -1
  196. package/plugins/qoder/skills/clipboard-vision/SKILL.md +47 -0
  197. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  198. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  199. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  200. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  201. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/SKILL.md +47 -0
  202. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  203. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  204. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  205. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  206. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  207. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  208. package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  209. package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  210. package/plugins/qoder/skills/delphi-review/.delphi-config.json.example +3 -11
  211. package/plugins/qoder/skills/delphi-review/AGENTS.md +15 -17
  212. package/plugins/qoder/skills/delphi-review/INSTALL.md +12 -12
  213. package/plugins/qoder/skills/delphi-review/SKILL.md +58 -25
  214. package/plugins/qoder/skills/delphi-review/opencode.json.delphi.example +1 -1
  215. package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +33 -49
  216. package/plugins/qoder/skills/sprint-flow/AGENTS.md +5 -5
  217. package/plugins/qoder/skills/sprint-flow/SKILL.md +2 -2
  218. package/plugins/qoder/skills/sprint-flow/references/force-levels.md +38 -35
  219. package/plugins/qoder/skills/sprint-flow/references/phase-1-prep.md +1 -1
  220. package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +48 -6
  221. package/plugins/qoder/skills/sprint-flow/references/phase-4-verify.md +5 -6
  222. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
  223. package/principles/AGENTS.md +4 -4
  224. package/scripts/__tests__/sync-package-content.test.js +30 -0
  225. package/scripts/delphi-external-review.cjs +194 -63
  226. package/scripts/sync-package-content.js +9 -2
  227. package/skills/clipboard-vision/SKILL.md +47 -0
  228. package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  229. package/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  230. package/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  231. package/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  232. package/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  233. package/skills/clipboard-vision/describe-clipboard.sh +161 -0
  234. package/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  235. package/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  236. package/skills/delphi-review/.delphi-config.json.example +24 -27
  237. package/skills/delphi-review/AGENTS.md +13 -12
  238. package/skills/delphi-review/INSTALL.md +15 -17
  239. package/skills/delphi-review/SKILL.md +30 -29
  240. package/skills/delphi-review/opencode.json.delphi.example +6 -10
  241. package/skills/delphi-review/references/code-walkthrough.md +33 -49
  242. package/skills/delphi-review/references/requirements.md +66 -54
  243. package/skills/delphi-review/references/round-templates.md +7 -7
  244. package/skills/sprint-flow/AGENTS.md +5 -5
  245. package/skills/sprint-flow/SKILL.md +4 -4
  246. package/skills/sprint-flow/references/components/skill-invocations.md +2 -2
  247. package/skills/sprint-flow/references/force-levels.md +37 -35
  248. package/skills/sprint-flow/references/orchestration-rules.md +1 -1
  249. package/skills/sprint-flow/references/phase-1-prep.md +1 -1
  250. package/skills/sprint-flow/references/phase-2-design.md +24 -14
  251. package/skills/sprint-flow/references/phase-4-verify.md +6 -6
  252. package/skills/sprint-flow/references/phase-overview.md +2 -2
  253. package/skills/test-specification-alignment/AGENTS.md +4 -4
  254. 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 return timedOut=true when process is killed (code=null)', async () => {
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(true);
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
- return false;
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 child = spawn('mutmut', ['run'], {
72
- stdio: 'pipe',
73
- shell: true,
74
- cwd: options.cwd,
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
- resolve({ report: null, timedOut: true });
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
- resolve({
147
+ settle({
106
148
  report,
107
- timedOut: false,
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
- resolve({ report: null, timedOut: false, error: err.message });
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.18.5",
3
+ "version": "0.19.0",
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.18.5",
3
+ "version": "0.19.0",
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": {