@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.
- package/build-integrity/__tests__/gate-10.test.ts +41 -18
- package/build-integrity/__tests__/orchestrator.test.ts +16 -2
- package/build-integrity/gate-10.ts +46 -10
- package/lib/__tests__/doctor.test.js +26 -0
- package/lib/__tests__/install-skill.test.js +3 -2
- package/lib/__tests__/ui-review.test.ts +19 -6
- package/lib/gate-runner.js +27 -6
- package/lib/ui-review.ts +32 -8
- package/lib/upgrade.js +4 -1
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/mutation/__tests__/pitest-runner.test.ts +1 -1
- package/mutation/__tests__/stryker-runner.test.ts +1 -1
- package/mutation/runners/go-mutant-runner.ts +1 -1
- package/mutation/runners/mutmut-runner.ts +1 -1
- package/mutation/runners/pitest-runner.ts +2 -2
- package/mutation/runners/stryker-runner.ts +2 -2
- package/package.json +2 -2
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/principles/__tests__/boy-scout.test.ts +1 -0
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/test-specification-alignment/AGENTS.md +3 -3
|
@@ -29,7 +29,15 @@ describe('runTscCheck', () => {
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
afterEach(async () => {
|
|
32
|
-
|
|
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
|
|
44
|
-
await fs.
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
|
245
|
-
|
|
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
|
-
//
|
|
182
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 () => {
|
package/lib/gate-runner.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
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));
|
package/mock-policy/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MOCK-POLICY KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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.
|
package/mutation/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MUTATION KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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:
|
|
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:
|
|
143
|
+
expect.objectContaining({ cwd: tmpDir, stdio: 'pipe', shell: true }),
|
|
144
144
|
);
|
|
145
145
|
|
|
146
146
|
expect(result.timedOut).toBe(false);
|
|
@@ -137,7 +137,7 @@ export class PitestRunner implements MutationRunner {
|
|
|
137
137
|
],
|
|
138
138
|
{
|
|
139
139
|
stdio: 'pipe',
|
|
140
|
-
shell:
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
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": "
|
|
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.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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.
|
package/principles/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# PRINCIPLES CHECKER MODULE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-18
|
|
4
|
+
**Commit:** 4724f8d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
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.
|