@boyingliu01/xp-gate 0.14.12 → 0.14.14

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 (35) hide show
  1. package/build-integrity/__tests__/gate-10.test.ts +41 -18
  2. package/build-integrity/__tests__/orchestrator.test.ts +16 -2
  3. package/build-integrity/gate-10.ts +46 -10
  4. package/lib/__tests__/doctor.test.js +26 -0
  5. package/lib/__tests__/install-skill.test.js +3 -2
  6. package/lib/__tests__/ui-review.test.ts +19 -6
  7. package/lib/gate-runner.js +27 -6
  8. package/lib/ui-review.ts +32 -8
  9. package/lib/upgrade.js +4 -1
  10. package/mock-policy/AGENTS.md +3 -3
  11. package/mutation/AGENTS.md +3 -3
  12. package/mutation/__tests__/pitest-runner.test.ts +1 -1
  13. package/mutation/__tests__/stryker-runner.test.ts +1 -1
  14. package/mutation/runners/go-mutant-runner.ts +1 -1
  15. package/mutation/runners/mutmut-runner.ts +1 -1
  16. package/mutation/runners/pitest-runner.ts +2 -2
  17. package/mutation/runners/stryker-runner.ts +2 -2
  18. package/package.json +2 -2
  19. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  20. package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
  21. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
  22. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  23. package/plugins/opencode/package.json +1 -1
  24. package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
  25. package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
  26. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  27. package/plugins/qoder/plugin.json +1 -1
  28. package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
  29. package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
  30. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  31. package/principles/AGENTS.md +3 -3
  32. package/principles/__tests__/boy-scout.test.ts +1 -0
  33. package/skills/delphi-review/AGENTS.md +3 -3
  34. package/skills/sprint-flow/AGENTS.md +3 -3
  35. package/skills/test-specification-alignment/AGENTS.md +3 -3
@@ -29,7 +29,15 @@ describe('runTscCheck', () => {
29
29
  });
30
30
 
31
31
  afterEach(async () => {
32
- await fs.rm(tmpDir, { recursive: true, force: true });
32
+ // Windows may hold a file lock from tsc subprocess; retry with backoff
33
+ for (let attempt = 0; attempt < 5; attempt++) {
34
+ try {
35
+ await fs.rm(tmpDir, { recursive: true, force: true });
36
+ break;
37
+ } catch {
38
+ if (attempt < 4) await new Promise(r => setTimeout(r, 500 * (attempt + 1)));
39
+ }
40
+ }
33
41
  });
34
42
 
35
43
  it('returns skip when no tsconfig.json exists', async () => {
@@ -40,22 +48,30 @@ describe('runTscCheck', () => {
40
48
  });
41
49
 
42
50
  it('returns skip when tsc is not available on PATH', async () => {
43
- // Create tsconfig.json but use a PATH without tsc
44
- await fs.writeFile(
45
- path.join(tmpDir, 'tsconfig.json'),
46
- JSON.stringify({ compilerOptions: { strict: true } })
47
- );
48
-
49
- // Override PATH to exclude node_modules/.bin
50
- const originalPath = process.env.PATH;
51
- process.env.PATH = '/usr/bin:/bin';
52
-
51
+ // Create tsconfig.json in an isolated dir with no node_modules
52
+ const isolatedDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gate10-notsc-'));
53
53
  try {
54
- const result = await runTscCheck(tmpDir, 30000);
55
- expect(result.status).toBe('skip');
56
- expect(result.message).toContain('tsc');
54
+ await fs.writeFile(
55
+ path.join(isolatedDir, 'tsconfig.json'),
56
+ JSON.stringify({ compilerOptions: { strict: true } })
57
+ );
58
+
59
+ // Override PATH and cwd to exclude all tsc resolution paths
60
+ const originalPath = process.env.PATH;
61
+ const originalCwd = process.cwd;
62
+ process.env.PATH = process.platform === 'win32' ? 'C:\\Windows\\System32' : '/usr/bin:/bin';
63
+ process.cwd = () => isolatedDir;
64
+
65
+ try {
66
+ const result = await runTscCheck(isolatedDir, 30000);
67
+ expect(result.status).toBe('skip');
68
+ expect(result.message).toContain('tsc');
69
+ } finally {
70
+ process.env.PATH = originalPath;
71
+ process.cwd = originalCwd;
72
+ }
57
73
  } finally {
58
- process.env.PATH = originalPath;
74
+ await fs.rm(isolatedDir, { recursive: true, force: true }).catch(() => {});
59
75
  }
60
76
  });
61
77
 
@@ -135,10 +151,10 @@ describe('runTscCheck', () => {
135
151
  await fs.writeFile(path.join(tmpDir, `file${i}.ts`), content);
136
152
  }
137
153
 
138
- // Very short timeout to force timeout
154
+ // Very short timeout to force timeout (or tsc unavailable on slow platforms)
139
155
  const result = await runTscCheck(tmpDir, 500);
140
156
  expect(result.status).toBe('skip');
141
- expect(result.message).toMatch(/timeout|Timeout|TIMEOUT|timed out/);
157
+ expect(result.message).toMatch(/timeout|Timeout|TIMEOUT|timed out|not available/i);
142
158
  });
143
159
  });
144
160
 
@@ -155,7 +171,14 @@ describe('runPackCheck', () => {
155
171
  });
156
172
 
157
173
  afterEach(async () => {
158
- await fs.rm(tmpDir, { recursive: true, force: true });
174
+ for (let attempt = 0; attempt < 5; attempt++) {
175
+ try {
176
+ await fs.rm(tmpDir, { recursive: true, force: true });
177
+ break;
178
+ } catch {
179
+ if (attempt < 4) await new Promise(r => setTimeout(r, 500 * (attempt + 1)));
180
+ }
181
+ }
159
182
  });
160
183
 
161
184
  it('returns skip when no package.json exists', async () => {
@@ -27,7 +27,14 @@ describe('runGate10', () => {
27
27
  });
28
28
 
29
29
  afterEach(async () => {
30
- await fs.rm(tmpDir, { recursive: true, force: true });
30
+ for (let attempt = 0; attempt < 5; attempt++) {
31
+ try {
32
+ await fs.rm(tmpDir, { recursive: true, force: true });
33
+ break;
34
+ } catch {
35
+ if (attempt < 4) await new Promise(r => setTimeout(r, 500 * (attempt + 1)));
36
+ }
37
+ }
31
38
  });
32
39
 
33
40
  it('returns skip status when no package.json exists', async () => {
@@ -223,7 +230,14 @@ describe('main', () => {
223
230
 
224
231
  afterEach(async () => {
225
232
  consoleSpy.mockRestore();
226
- await fs.rm(tmpDir, { recursive: true, force: true });
233
+ for (let attempt = 0; attempt < 5; attempt++) {
234
+ try {
235
+ await fs.rm(tmpDir, { recursive: true, force: true });
236
+ break;
237
+ } catch {
238
+ if (attempt < 4) await new Promise(r => setTimeout(r, 500 * (attempt + 1)));
239
+ }
240
+ }
227
241
  });
228
242
 
229
243
  it('returns 0 when no changed files provided', async () => {
@@ -65,13 +65,42 @@ export async function runTscCheck(
65
65
  };
66
66
  }
67
67
 
68
- // Check if tsc is available (run from projectRoot where node_modules should exist)
68
+ // Resolve tsc binary: try multiple strategies for cross-platform reliability
69
+ function findTsc(): string | null {
70
+ // Strategy 1: projectRoot/node_modules/typescript/bin/tsc
71
+ const direct = path.join(projectRoot, 'node_modules', 'typescript', 'bin', 'tsc');
72
+ try { fsNative.accessSync(direct); return direct; } catch { /* continue */ }
73
+
74
+ // Strategy 2: resolve from cwd's node_modules (works when test symlinks or in project)
75
+ const fromCwd = path.join(process.cwd(), 'node_modules', 'typescript', 'bin', 'tsc');
76
+ try { fsNative.accessSync(fromCwd); return fromCwd; } catch { /* continue */ }
77
+
78
+ // Strategy 3: resolve via require.resolve from this file's location
79
+ try {
80
+ const resolved = require.resolve('typescript/bin/tsc', { paths: [projectRoot, process.cwd()] });
81
+ try { fsNative.accessSync(resolved); return resolved; } catch { /* continue */ }
82
+ } catch { /* continue */ }
83
+
84
+ return null;
85
+ }
86
+
87
+ const resolvedTsc = findTsc();
88
+
89
+ // Check if tsc is available
69
90
  try {
70
- await execFileAsync('npx', ['tsc', '--version'], {
71
- cwd: projectRoot,
72
- timeout: 5000,
73
- env: { ...process.env, PATH: process.env.PATH },
74
- });
91
+ if (resolvedTsc) {
92
+ await execFileAsync(process.execPath, [resolvedTsc, '--version'], {
93
+ cwd: projectRoot,
94
+ timeout: 10000,
95
+ });
96
+ } else {
97
+ await execFileAsync('npx', ['tsc', '--version'], {
98
+ cwd: projectRoot,
99
+ timeout: 10000,
100
+ shell: true,
101
+ env: { ...process.env, PATH: process.env.PATH },
102
+ });
103
+ }
75
104
  } catch {
76
105
  return {
77
106
  status: 'skip',
@@ -82,12 +111,17 @@ export async function runTscCheck(
82
111
 
83
112
  // Run tsc --noEmit --incremental
84
113
  try {
114
+ const tscCmd = resolvedTsc ? process.execPath : 'npx';
115
+ const tscArgs = resolvedTsc
116
+ ? [resolvedTsc, '--noEmit', '--incremental']
117
+ : ['tsc', '--noEmit', '--incremental'];
85
118
  const { stdout, stderr } = await execFileAsync(
86
- 'npx',
87
- ['tsc', '--noEmit', '--incremental'],
119
+ tscCmd,
120
+ tscArgs,
88
121
  {
89
122
  cwd: projectRoot,
90
123
  timeout: timeoutMs,
124
+ shell: !resolvedTsc,
91
125
  maxBuffer: 10 * 1024 * 1024, // 10MB
92
126
  }
93
127
  );
@@ -241,8 +275,9 @@ export function resolveImportPath(importPath: string, fromFile: string): string
241
275
  if (tryAccess(resolved + tryExt)) return resolved + tryExt;
242
276
  }
243
277
 
244
- for (const idx of ['/index.ts', '/index.js']) {
245
- if (tryAccess(resolved + idx)) return resolved + idx;
278
+ for (const idxName of ['index.ts', 'index.js']) {
279
+ const candidate = path.join(resolved, idxName);
280
+ if (tryAccess(candidate)) return candidate;
246
281
  }
247
282
 
248
283
  return null;
@@ -369,6 +404,7 @@ export async function runPackCheck(
369
404
  const { stdout } = await execFileAsync('npm', ['pack', '--dry-run', '--json'], {
370
405
  cwd: projectRoot,
371
406
  timeout: timeoutMs,
407
+ shell: true,
372
408
  maxBuffer: 10 * 1024 * 1024,
373
409
  });
374
410
 
@@ -492,6 +492,32 @@ describe('doctor', () => {
492
492
  }
493
493
  return '';
494
494
  };
495
+ // Async exec mock for execWithTimeout (util.promisify(exec) in doctor.js)
496
+ const mockAsyncExec = (cmd, opts, callback) => {
497
+ if (typeof opts === 'function') { callback = opts; opts = {}; }
498
+ if (cmd === 'git rev-parse --git-dir') {
499
+ callback(null, path.join(tmpProject, '.git') + '\n', '');
500
+ } else if (cmd.includes('git config --global core.hooksPath')) {
501
+ callback(null, cmd.includes('--unset') ? '' : '/wrong/path\n', '');
502
+ } else if (cmd === 'node --version') {
503
+ callback(null, 'v20.0.0\n', '');
504
+ } else if (cmd === 'git --version') {
505
+ callback(null, 'git version 2.39.0\n', '');
506
+ } else if (cmd === 'bash --version') {
507
+ callback(null, 'GNU bash, version 5.1.16\n', '');
508
+ } else {
509
+ callback(null, '', '');
510
+ }
511
+ };
512
+ mockAsyncExec[util.promisify.custom] = (cmd, options) => {
513
+ return new Promise((resolve, reject) => {
514
+ mockAsyncExec(cmd, options, (err, stdout, stderr) => {
515
+ if (err) { err.stdout = stdout || ''; err.stderr = stderr || ''; reject(err); }
516
+ else { resolve({ stdout, stderr }); }
517
+ });
518
+ });
519
+ };
520
+ cp.exec = mockAsyncExec;
495
521
  const { doctor } = require('../doctor');
496
522
 
497
523
  const result = await doctor(['--fix']);
@@ -178,8 +178,9 @@ describe('install-skill', () => {
178
178
  expect(fs.existsSync(configFile)).toBe(true);
179
179
  const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
180
180
  expect(config.installedSkills).toHaveProperty('delphi-review');
181
- // Version should be actual CLI version (0.14.11.0), not hardcoded '1.0.0'
182
- expect(config.installedSkills['delphi-review'].version).toBe('0.14.11.0');
181
+ // Read version dynamically from VERSION file — no hardcoded version to break on bump
182
+ const expectedVersion = fs.readFileSync(path.join(__dirname, '..', '..', '..', '..', 'VERSION'), 'utf8').trim();
183
+ expect(config.installedSkills['delphi-review'].version).toBe(expectedVersion);
183
184
  expect(config.installedSkills['delphi-review'].installedAt).toMatch(/^\d{4}-/);
184
185
  });
185
186
 
@@ -17,7 +17,7 @@ const TEST_DIR = join(process.cwd(), '.ui-review-test');
17
17
 
18
18
  describe('ui-review', () => {
19
19
  beforeEach(() => {
20
- vi.clearAllMocks();
20
+ vi.resetAllMocks();
21
21
  if (existsSync(TEST_DIR)) {
22
22
  rmSync(TEST_DIR, { recursive: true, force: true });
23
23
  }
@@ -52,13 +52,26 @@ describe('ui-review', () => {
52
52
  expect(getChangedFilesForReview()).toEqual(['src/components/Fallback.tsx']);
53
53
  });
54
54
 
55
- it('should fall back to find command when git ls-files fails', async () => {
55
+ it('should fall back to filesystem scan when git ls-files fails', async () => {
56
+ // Set up a temp directory with known UI files for the filesystem walk
57
+ const scanDir = join(TEST_DIR, 'scan-test');
58
+ mkdirSync(join(scanDir, 'views'), { recursive: true });
59
+ mkdirSync(join(scanDir, 'src'), { recursive: true });
60
+ existsSync(join(scanDir, 'views', 'index.html')) || require('fs').writeFileSync(join(scanDir, 'views', 'index.html'), '<html></html>');
61
+ require('fs').writeFileSync(join(scanDir, 'src', 'app.ts'), 'export const x = 1;');
62
+
63
+ const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(scanDir);
56
64
  mockExecSync
57
- .mockReturnValueOnce('')
58
- .mockImplementationOnce(() => { throw new Error('not git'); })
59
- .mockReturnValueOnce('views/index.html\n');
65
+ .mockReturnValueOnce('') // git diff → no changes
66
+ .mockImplementationOnce(() => { throw new Error('not git'); }); // git ls-files fails
67
+
60
68
  const { getChangedFilesForReview } = await import('../ui-review');
61
- expect(getChangedFilesForReview()).toEqual(['views/index.html']);
69
+ const result = getChangedFilesForReview();
70
+ // Should find files via filesystem walk (cross-platform, no find command)
71
+ expect(result).toContain('views/index.html'.replace(/\//g, require('path').sep));
72
+ expect(result).toContain('src/app.ts'.replace(/\//g, require('path').sep));
73
+ expect(result.length).toBe(2);
74
+ cwdSpy.mockRestore();
62
75
  });
63
76
 
64
77
  it('should build an approved review result with 24h expiry', async () => {
@@ -4,6 +4,27 @@ const { execSync, execFileSync } = require('child_process');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
6
 
7
+ /**
8
+ * Run a bash shell script cross-platform.
9
+ * On Unix: uses bash directly.
10
+ * On Windows: tries bash from PATH (Git Bash / WSL), falls back to a clear message.
11
+ */
12
+ function runBashScript(scriptPath) {
13
+ if (process.platform === 'win32') {
14
+ // Check if bash is available on Windows (Git Bash, MSYS2, WSL)
15
+ try {
16
+ execSync('bash --version', { stdio: 'pipe', timeout: 5000 });
17
+ } catch {
18
+ console.log('⚠️ This gate requires bash to run shell scripts.');
19
+ console.log(' On Windows, install Git for Windows (includes Git Bash) or enable WSL.');
20
+ console.log(' Alternatively, install the required tool directly and run the gate again.');
21
+ return;
22
+ }
23
+ }
24
+ // bash is available — run the script (Unix always, or Windows after check above)
25
+ execSync(`bash "${scriptPath}"`, { stdio: 'inherit' });
26
+ }
27
+
7
28
  // Gate metadata registry — maps gate IDs to names, descriptions, and how to run them.
8
29
  // Standalone gates (<gate-id>: { run }) are invokable via xp-gate gate-<id>.
9
30
  // Pre-commit-only gates (<gate-id>: { preCommitOnly: true }) run only in git commit context.
@@ -30,7 +51,7 @@ const GATE_REGISTRY = {
30
51
  const jscpdConfig = findConfig(targetPath, 'jscpd.conf.json');
31
52
  const args = [targetPath || '.'];
32
53
  if (jscpdConfig) args.push('--config', jscpdConfig);
33
- execFileSync('npx', ['-y', 'jscpd', ...args], { stdio: 'inherit' });
54
+ execFileSync('npx', ['-y', 'jscpd', ...args], { stdio: 'inherit', shell: true });
34
55
  },
35
56
  },
36
57
  '3': {
@@ -40,11 +61,11 @@ const GATE_REGISTRY = {
40
61
  run: (targetPath) => {
41
62
  const script = resolveGateScript('3');
42
63
  if (script) {
43
- execSync(`bash "${script}"`, { stdio: 'inherit' });
64
+ runBashScript(script);
44
65
  } else {
45
66
  const target = targetPath || '.';
46
67
  console.log(`Running lizard on ${target}...`);
47
- execFileSync('lizard', [target, '--CCN', '10', '--length', '50', '--arguments', '5', '--warnings_only'], { stdio: 'inherit' });
68
+ execFileSync('lizard', [target, '--CCN', '10', '--length', '50', '--arguments', '5', '--warnings_only'], { stdio: 'inherit', shell: true });
48
69
  }
49
70
  },
50
71
  },
@@ -80,7 +101,7 @@ const GATE_REGISTRY = {
80
101
  run: (targetPath) => {
81
102
  const script = resolveGateScript('7');
82
103
  if (script) {
83
- execSync(`bash "${script}"`, { stdio: 'inherit' });
104
+ runBashScript(script);
84
105
  } else {
85
106
  console.log('IaC security scan requires git-staged context for changed files detection.');
86
107
  console.log('Run: git commit to trigger Gate 7 automatically.');
@@ -94,7 +115,7 @@ const GATE_REGISTRY = {
94
115
  run: (targetPath) => {
95
116
  const script = resolveGateScript('8');
96
117
  if (script) {
97
- execSync(`bash "${script}"`, { stdio: 'inherit' });
118
+ runBashScript(script);
98
119
  } else {
99
120
  console.log('Secret scanning requires git-staged context for changed files detection.');
100
121
  console.log('Run: git commit to trigger Gate 8 automatically.');
@@ -108,7 +129,7 @@ const GATE_REGISTRY = {
108
129
  run: (targetPath) => {
109
130
  const script = resolveGateScript('9');
110
131
  if (script) {
111
- execSync(`bash "${script}"`, { stdio: 'inherit' });
132
+ runBashScript(script);
112
133
  } else {
113
134
  console.log('SAST scanning requires git-staged context for changed files detection.');
114
135
  console.log('Run: git commit to trigger Gate 9 automatically.');
package/lib/ui-review.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { execSync } from 'child_process';
2
- import { writeFileSync } from 'fs';
3
- import { join } from 'path';
2
+ import { writeFileSync, readdirSync, statSync } from 'fs';
3
+ import { join, relative, extname } from 'path';
4
4
  import { collectUiMatches, parseRenamedFile } from './ui-detector';
5
5
 
6
6
  const RESULT_FILE = '.ui-gate-result.json';
@@ -42,11 +42,31 @@ export function getFallbackFileList(): string[] {
42
42
  try {
43
43
  return parseFileList(execSync('git ls-files', { encoding: 'utf8' }).trim());
44
44
  } catch {
45
- const output = execSync(
46
- 'find . -maxdepth 3 -type f -name "*.ts" -o -name "*.html" -o -name "*.css" -o -name "*.scss" -o -name "*.tsx" -o -name "*.vue" -o -name "*.svelte" 2>/dev/null | grep -v node_modules | grep -v .git',
47
- { encoding: 'utf8' },
48
- ).trim();
49
- return parseFileList(output);
45
+ // Not a git repo — fall back to filesystem scan (cross-platform)
46
+ const UI_EXTENSIONS = new Set(['.ts', '.html', '.css', '.scss', '.tsx', '.vue', '.svelte']);
47
+ const results: string[] = [];
48
+
49
+ function walk(dir: string, depth: number) {
50
+ if (depth > 3) return;
51
+ try {
52
+ const entries = readdirSync(dir);
53
+ for (const entry of entries) {
54
+ if (entry === 'node_modules' || entry === '.git') continue;
55
+ const fullPath = join(dir, entry);
56
+ try {
57
+ const st = statSync(fullPath);
58
+ if (st.isDirectory()) {
59
+ walk(fullPath, depth + 1);
60
+ } else if (st.isFile() && UI_EXTENSIONS.has(extname(entry))) {
61
+ results.push(relative(process.cwd(), fullPath));
62
+ }
63
+ } catch { /* skip inaccessible entries */ }
64
+ }
65
+ } catch { /* skip inaccessible directories */ }
66
+ }
67
+
68
+ walk(process.cwd(), 0);
69
+ return results;
50
70
  }
51
71
  }
52
72
 
@@ -69,7 +89,11 @@ export function writeUiReviewResult(result: UiReviewResult, repoRoot: string = p
69
89
  }
70
90
 
71
91
  function getCurrentCommit(): string {
72
- return execSync('git rev-parse HEAD 2>/dev/null || echo "no-commit"', { encoding: 'utf8' }).trim();
92
+ try {
93
+ return execSync('git rev-parse HEAD', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
94
+ } catch {
95
+ return 'no-commit';
96
+ }
73
97
  }
74
98
 
75
99
  export function main(): void {
package/lib/upgrade.js CHANGED
@@ -80,6 +80,7 @@ async function handleApplyMode(result, pkgName) {
80
80
  try {
81
81
  const child = spawn('npm', ['install', '-g', `${pkgName}@${result.remote}`], {
82
82
  stdio: 'inherit',
83
+ shell: true,
83
84
  timeout: 120000,
84
85
  });
85
86
  await new Promise((resolve, reject) => {
@@ -125,7 +126,7 @@ async function upgradeOpenCodePlugin() {
125
126
  const hasPlugin = await hasOpenCodePlugin();
126
127
  if (!hasPlugin) return;
127
128
 
128
- const { stdout: versionOut } = await execAsync('npm list -g ' + OPENCODE_PLUGIN + ' --depth=0 --json 2>/dev/null');
129
+ const { stdout: versionOut } = await execAsync('npm list -g ' + OPENCODE_PLUGIN + ' --depth=0 --json');
129
130
  let currentVersion = '';
130
131
  try {
131
132
  const parsed = JSON.parse(versionOut);
@@ -138,6 +139,7 @@ async function upgradeOpenCodePlugin() {
138
139
 
139
140
  const child = spawn('npm', ['install', '-g', OPENCODE_PLUGIN + '@latest'], {
140
141
  stdio: 'pipe',
142
+ shell: true,
141
143
  timeout: 120000,
142
144
  });
143
145
  const exitCode = await new Promise((resolve) => {
@@ -156,6 +158,7 @@ function hasOpenCodePlugin() {
156
158
  return new Promise((resolve) => {
157
159
  const child = spawn('npm', ['list', '-g', OPENCODE_PLUGIN, '--depth=0'], {
158
160
  stdio: 'pipe',
161
+ shell: true,
159
162
  timeout: 10000,
160
163
  });
161
164
  child.on('close', (code) => resolve(code === 0));
@@ -1,9 +1,9 @@
1
1
  # SRC/MOCK-POLICY KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.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.
@@ -1,9 +1,9 @@
1
1
  # SRC/MUTATION KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.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.
@@ -252,7 +252,7 @@ describe('PitestRunner', () => {
252
252
  expect(spawn).toHaveBeenCalledWith(join(tmpDir, 'gradlew'), ['pitest'], {
253
253
  cwd: tmpDir,
254
254
  stdio: 'pipe',
255
- shell: false,
255
+ shell: true,
256
256
  });
257
257
  expect(result.timedOut).toBe(false);
258
258
  expect(result.report).not.toBeNull();
@@ -140,7 +140,7 @@ describe('StrykerRunner', () => {
140
140
  '--mutate', 'src/a.ts',
141
141
  '--mutate', 'src/b.tsx',
142
142
  ],
143
- expect.objectContaining({ cwd: tmpDir, stdio: 'pipe', shell: false }),
143
+ expect.objectContaining({ cwd: tmpDir, stdio: 'pipe', shell: true }),
144
144
  );
145
145
 
146
146
  expect(result.timedOut).toBe(false);
@@ -47,7 +47,7 @@ export class GoMutantRunner implements MutationRunner {
47
47
 
48
48
  const child = spawn('gomutants', args, {
49
49
  stdio: 'pipe',
50
- shell: false,
50
+ shell: true,
51
51
  cwd: options.cwd,
52
52
  });
53
53
 
@@ -70,7 +70,7 @@ export class MutmutRunner implements MutationRunner {
70
70
  return new Promise((resolve) => {
71
71
  const child = spawn('mutmut', ['run'], {
72
72
  stdio: 'pipe',
73
- shell: false,
73
+ shell: true,
74
74
  cwd: options.cwd,
75
75
  });
76
76
 
@@ -137,7 +137,7 @@ export class PitestRunner implements MutationRunner {
137
137
  ],
138
138
  {
139
139
  stdio: 'pipe',
140
- shell: false,
140
+ shell: true,
141
141
  cwd: options.cwd,
142
142
  },
143
143
  );
@@ -190,7 +190,7 @@ export class PitestRunner implements MutationRunner {
190
190
 
191
191
  const child = spawn(gradleCmd, ['pitest'], {
192
192
  stdio: 'pipe',
193
- shell: false,
193
+ shell: true,
194
194
  cwd: options.cwd,
195
195
  });
196
196
 
@@ -25,7 +25,7 @@ export class StrykerRunner implements MutationRunner {
25
25
  const { spawnSync } = await import('child_process');
26
26
  const result = spawnSync('npx', ['stryker', '--version'], {
27
27
  stdio: 'pipe',
28
- shell: false,
28
+ shell: true,
29
29
  timeout: 5000,
30
30
  });
31
31
  return result.status !== null && result.status === 0;
@@ -46,7 +46,7 @@ export class StrykerRunner implements MutationRunner {
46
46
 
47
47
  const child = spawn('npx', args, {
48
48
  stdio: 'pipe',
49
- shell: false,
49
+ shell: true,
50
50
  cwd: options.cwd,
51
51
  });
52
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/xp-gate",
3
- "version": "0.14.12",
3
+ "version": "0.14.14",
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"
@@ -38,7 +38,7 @@
38
38
  "node": ">=18.0.0"
39
39
  },
40
40
  "scripts": {
41
- "version": "node scripts/sync-package-content.js && git add -A skills/ plugins/ 2>/dev/null || true",
41
+ "version": "0.14.14",
42
42
  "prepack": "node scripts/sync-package-content.js",
43
43
  "test": "echo \"Run tests\" && exit 0"
44
44
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.14.11",
3
+ "version": "0.14.12",
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": {
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/opencode-plugin",
3
- "version": "0.14.12",
3
+ "version": "0.14.14",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "description": "XP-Gate quality gates + AI workflow skills + Sprint Flow TUI sidebar for OpenCode",
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.14.11",
3
+ "version": "0.14.12",
4
4
  "displayName": "XP-Gate",
5
5
  "description": "Extreme Programming quality gates + AI workflow skills for Qoder. Includes 10 quality gates (Gate 0-9), Sprint Flow (11 phases), and Delphi multi-expert review (>=90% consensus).",
6
6
  "author": {
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,9 +1,9 @@
1
1
  # PRINCIPLES CHECKER MODULE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Clean Code & SOLID principles checker — **Gate 4** of pre-commit. 14 rules × 9 language adapters, SARIF 2.1.0 output. Houses the **Boy Scout Rule** enforcement engine (Gate 6) and warning-baseline storage.
@@ -569,6 +569,7 @@ describe('Boy Scout Rule Enforcement', () => {
569
569
  const result = spawnSync('npx', ['tsx', BOY_SCOUT_PATH, ...args], {
570
570
  cwd: cwd ?? process.cwd(),
571
571
  encoding: 'utf-8',
572
+ shell: true,
572
573
  env: { ...process.env, NODE_V8_COVERAGE: coverageDir },
573
574
  });
574
575
  return {
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-16
4
- **Commit:** 2f70dd5
3
+ **Generated:** 2026-07-18
4
+ **Commit:** 4724f8d
5
5
  **Branch:** main
6
- **Version:** 0.14.11.0
6
+ **Version:** 0.14.12.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.