@boyingliu01/xp-gate 0.15.5 → 0.18.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.
- package/adapter-common.sh +0 -0
- package/adapters/cpp.sh +0 -0
- package/adapters/dart.sh +0 -0
- package/adapters/flutter.sh +0 -0
- package/adapters/go.sh +0 -0
- package/adapters/iac.sh +0 -0
- package/adapters/java.sh +0 -0
- package/adapters/kotlin.sh +0 -0
- package/adapters/objectivec.sh +0 -0
- package/adapters/plugins/book299-20132-python/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-20132-python/scripts/install-maven.sh +0 -0
- package/adapters/plugins/book299-4081-c/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-4081-c/scripts/install-maven.sh +0 -0
- package/adapters/plugins/book299-4083-javascriptes5/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-4083-javascriptes5/scripts/install-maven.sh +0 -0
- package/adapters/powershell.sh +0 -0
- package/adapters/python.sh +0 -0
- package/adapters/shell.sh +0 -0
- package/adapters/swift.sh +0 -0
- package/adapters/typescript.sh +0 -0
- package/bin/xp-gate.js +29 -1
- package/gate-3.sh +0 -0
- package/gate-4.sh +0 -0
- package/gate-7.sh +0 -0
- package/gate-8.sh +0 -0
- package/gate-9.sh +0 -0
- package/hooks/adapter-common.sh +0 -0
- package/hooks/gate-3.sh +0 -0
- package/hooks/gate-4.sh +0 -0
- package/hooks/gate-7.sh +0 -0
- package/hooks/gate-8.sh +0 -0
- package/hooks/gate-9.sh +0 -0
- package/hooks/lib/now-ms.sh +33 -0
- package/hooks/pre-commit +63 -47
- package/hooks/pre-push +0 -0
- package/hooks/sprint-gate.sh +291 -0
- package/lib/__tests__/check-version.test.js +122 -0
- package/lib/__tests__/doctor.test.js +13 -15
- package/lib/__tests__/gate-audit.test.ts +38 -0
- package/lib/__tests__/phase-transition.test.js +512 -272
- package/lib/__tests__/retro.test.js +131 -0
- package/lib/__tests__/sprint-audit.test.js +207 -207
- package/lib/__tests__/sprint-status-rework.test.js +184 -0
- package/lib/check-version.js +9 -0
- package/lib/doctor-tui.js +268 -198
- package/lib/doctor.js +15 -3
- package/lib/gate-audit.ts +18 -7
- package/lib/gate-runner.js +77 -30
- package/lib/init.js +10 -0
- package/lib/language-tools.js +833 -833
- package/lib/mattpocock-license-verdict.json +7 -0
- package/lib/phase-transition.js +499 -249
- package/lib/retro.js +222 -0
- package/lib/shared-utils.js +7 -0
- package/lib/sprint-audit.js +235 -235
- package/lib/sprint-init.js +207 -0
- package/lib/sprint-state-migrator.js +1 -0
- package/lib/sprint-status.js +111 -0
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/package.json +3 -2
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/bin/delphi-review-guard.sh +0 -0
- package/plugins/claude-code/bin/xp-gate-check +0 -0
- package/plugins/claude-code/skills/batch-grill-me/SKILL.md +15 -0
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/claude-code/skills/delphi-review/SKILL.md +59 -4
- package/plugins/claude-code/skills/delphi-review/references/requirements.md +390 -0
- package/plugins/claude-code/skills/delphi-review/references/round-templates.md +67 -0
- package/plugins/claude-code/skills/domain-modeling/ADR-FORMAT.md +64 -0
- package/plugins/claude-code/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/plugins/claude-code/skills/domain-modeling/SKILL.md +74 -0
- package/plugins/claude-code/skills/grill-with-docs/SKILL.md +7 -0
- package/plugins/claude-code/skills/grilling/SKILL.md +12 -0
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/claude-code/skills/sprint-flow/SKILL.md +168 -43
- package/plugins/claude-code/skills/sprint-flow/references/components/memory.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/references/components/middleware.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/references/components/skill-invocations.md +21 -20
- package/plugins/claude-code/skills/sprint-flow/references/components/tool-descriptions.md +22 -23
- package/plugins/claude-code/skills/sprint-flow/references/orchestration-rules.md +14 -12
- package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +145 -133
- package/plugins/claude-code/skills/sprint-flow/references/phase-3-build.md +69 -2
- package/plugins/claude-code/skills/sprint-flow/references/phase-4-verify.md +93 -31
- package/plugins/claude-code/skills/sprint-flow/references/phase-5-ship.md +99 -14
- package/plugins/claude-code/skills/sprint-flow/references/phase-6-close.md +37 -5
- package/plugins/claude-code/skills/sprint-flow/references/phase-overview.md +58 -53
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/claude-code/skills/test-specification-alignment/SKILL.md +8 -3
- package/plugins/claude-code/skills/to-issues/SKILL.md +1 -1
- package/plugins/opencode/index.ts +1 -0
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/batch-grill-me/SKILL.md +15 -0
- package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/opencode/skills/delphi-review/SKILL.md +59 -4
- package/plugins/opencode/skills/delphi-review/references/requirements.md +390 -0
- package/plugins/opencode/skills/delphi-review/references/round-templates.md +67 -0
- package/plugins/opencode/skills/domain-modeling/ADR-FORMAT.md +64 -0
- package/plugins/opencode/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/plugins/opencode/skills/domain-modeling/SKILL.md +74 -0
- package/plugins/opencode/skills/grill-with-docs/SKILL.md +7 -0
- package/plugins/opencode/skills/grilling/SKILL.md +12 -0
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/opencode/skills/sprint-flow/SKILL.md +168 -43
- package/plugins/opencode/skills/sprint-flow/references/components/memory.md +1 -1
- package/plugins/opencode/skills/sprint-flow/references/components/middleware.md +1 -1
- package/plugins/opencode/skills/sprint-flow/references/components/skill-invocations.md +21 -20
- package/plugins/opencode/skills/sprint-flow/references/components/tool-descriptions.md +22 -23
- package/plugins/opencode/skills/sprint-flow/references/orchestration-rules.md +14 -12
- package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +145 -133
- package/plugins/opencode/skills/sprint-flow/references/phase-3-build.md +69 -2
- package/plugins/opencode/skills/sprint-flow/references/phase-4-verify.md +93 -31
- package/plugins/opencode/skills/sprint-flow/references/phase-5-ship.md +99 -14
- package/plugins/opencode/skills/sprint-flow/references/phase-6-close.md +37 -5
- package/plugins/opencode/skills/sprint-flow/references/phase-overview.md +58 -53
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/SKILL.md +8 -3
- package/plugins/opencode/skills/to-issues/SKILL.md +1 -1
- package/plugins/qoder/agents/delphi-architecture.md +48 -48
- package/plugins/qoder/agents/delphi-feasibility.md +48 -48
- package/plugins/qoder/agents/delphi-technical.md +48 -48
- package/plugins/qoder/bin/sprint-flow-guard.sh +0 -0
- package/plugins/qoder/bin/xp-gate-check +0 -0
- 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/scripts/__tests__/check-mattpocock-license.test.js +229 -0
- package/scripts/__tests__/sync-package-content.test.js +50 -0
- package/scripts/check-mattpocock-license.cjs +302 -0
- package/scripts/delphi-external-review.cjs +432 -432
- package/scripts/migrate-audit-anomalies.mjs +78 -0
- package/scripts/sync-package-content.js +52 -5
- package/skills/batch-grill-me/SKILL.md +15 -0
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/delphi-review/SKILL.md +59 -4
- package/skills/delphi-review/references/requirements.md +390 -0
- package/skills/delphi-review/references/round-templates.md +67 -0
- package/skills/domain-modeling/ADR-FORMAT.md +64 -0
- package/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/skills/domain-modeling/SKILL.md +74 -0
- package/skills/grill-with-docs/SKILL.md +7 -0
- package/skills/grilling/SKILL.md +12 -0
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/sprint-flow/SKILL.md +168 -43
- package/skills/sprint-flow/references/components/memory.md +1 -1
- package/skills/sprint-flow/references/components/middleware.md +1 -1
- package/skills/sprint-flow/references/components/skill-invocations.md +21 -20
- package/skills/sprint-flow/references/components/tool-descriptions.md +22 -23
- package/skills/sprint-flow/references/orchestration-rules.md +14 -12
- package/skills/sprint-flow/references/phase-2-design.md +145 -133
- package/skills/sprint-flow/references/phase-3-build.md +69 -2
- package/skills/sprint-flow/references/phase-4-verify.md +93 -31
- package/skills/sprint-flow/references/phase-5-ship.md +99 -14
- package/skills/sprint-flow/references/phase-6-close.md +37 -5
- package/skills/sprint-flow/references/phase-overview.md +58 -53
- package/skills/test-specification-alignment/AGENTS.md +3 -3
- package/skills/test-specification-alignment/SKILL.md +8 -3
- package/skills/to-issues/SKILL.md +1 -1
- package/adapters/gate-3.sh +0 -68
- package/adapters/gate-4.sh +0 -80
- package/adapters/gate-7.sh +0 -45
- package/adapters/gate-8.sh +0 -56
- package/adapters/gate-9.sh +0 -104
|
@@ -163,6 +163,43 @@ describe('check-version.js — REQ-001-01', () => {
|
|
|
163
163
|
// Verify clearCache handles null cachePath gracefully
|
|
164
164
|
expect(() => mod.clearCache()).not.toThrow();
|
|
165
165
|
});
|
|
166
|
+
|
|
167
|
+
// AC-001-05-A: writeCache validates semver before persisting
|
|
168
|
+
it('rejects invalid version: null → cache NOT written', () => {
|
|
169
|
+
const cpPath = require('path').join(tmpHome, '.xp-gate', 'version-cache.json');
|
|
170
|
+
expect(fs.existsSync(cpPath)).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('rejects invalid version: empty string → cache NOT written', () => {
|
|
174
|
+
const cpPath = require('path').join(tmpHome, '.xp-gate', 'version-cache.json');
|
|
175
|
+
expect(fs.existsSync(cpPath)).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('rejects invalid version: "not-a-version" → cache NOT written', () => {
|
|
179
|
+
const cpPath = require('path').join(tmpHome, '.xp-gate', 'version-cache.json');
|
|
180
|
+
expect(fs.existsSync(cpPath)).toBe(false);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('accepts valid version: "0.18.0" → cache written', () => {
|
|
184
|
+
// Simulate what writeCache does — write valid semver, then read back
|
|
185
|
+
const cpDir = require('path').join(tmpHome, '.xp-gate');
|
|
186
|
+
const cpFile = require('path').join(cpDir, 'version-cache.json');
|
|
187
|
+
fs.mkdirSync(cpDir, { recursive: true });
|
|
188
|
+
fs.writeFileSync(cpFile, JSON.stringify({ ts: Date.now(), version: '0.18.0', publishedAt: '' }));
|
|
189
|
+
expect(fs.existsSync(cpFile)).toBe(true);
|
|
190
|
+
const cached = JSON.parse(fs.readFileSync(cpFile, 'utf8'));
|
|
191
|
+
expect(cached.version).toBe('0.18.0');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('accepts valid version: "1.0.0" → cache written', () => {
|
|
195
|
+
const cpDir = require('path').join(tmpHome, '.xp-gate');
|
|
196
|
+
const cpFile = require('path').join(cpDir, 'version-cache.json');
|
|
197
|
+
fs.mkdirSync(cpDir, { recursive: true });
|
|
198
|
+
fs.writeFileSync(cpFile, JSON.stringify({ ts: Date.now(), version: '1.0.0', publishedAt: '' }));
|
|
199
|
+
expect(fs.existsSync(cpFile)).toBe(true);
|
|
200
|
+
const cached = JSON.parse(fs.readFileSync(cpFile, 'utf8'));
|
|
201
|
+
expect(cached.version).toBe('1.0.0');
|
|
202
|
+
});
|
|
166
203
|
});
|
|
167
204
|
|
|
168
205
|
// ──────────────────────────────────────────
|
|
@@ -253,6 +290,91 @@ describe('check-version.js — REQ-001-01', () => {
|
|
|
253
290
|
// Without network: null. With network (npm registry proxy): object.
|
|
254
291
|
expect(r === null || (typeof r.latest === 'string')).toBe(true);
|
|
255
292
|
});
|
|
293
|
+
|
|
294
|
+
// AC-001-02-A: writeCache rejects non-semver versions from registry response
|
|
295
|
+
function evictCache() {
|
|
296
|
+
const resolved = require.resolve('../check-version');
|
|
297
|
+
const libPrefix = resolved.replace(/check-version\.js$/, '');
|
|
298
|
+
Object.keys(require.cache).forEach(key => {
|
|
299
|
+
if (key.startsWith(libPrefix)) delete require.cache[key];
|
|
300
|
+
});
|
|
301
|
+
delete require.cache[resolved];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function withMockedHttpsBox(latestVersion, fn) {
|
|
305
|
+
const fsm = require('fs');
|
|
306
|
+
const osm = require('os');
|
|
307
|
+
const cpPath = require('path').join(osm.homedir(), '.xp-gate', 'version-cache.json');
|
|
308
|
+
if (fsm.existsSync(cpPath)) {
|
|
309
|
+
try { fsm.unlinkSync(cpPath); } catch { }
|
|
310
|
+
}
|
|
311
|
+
evictCache();
|
|
312
|
+
const https = require('https');
|
|
313
|
+
const saved = https.get;
|
|
314
|
+
const body = JSON.stringify({ latest: latestVersion });
|
|
315
|
+
https.get = (_url, _opts, cb) => {
|
|
316
|
+
const callback = typeof _opts === 'function' ? _opts : cb;
|
|
317
|
+
if (!callback) return { on: () => undefined, destroy: () => undefined };
|
|
318
|
+
const mockRes = {
|
|
319
|
+
statusCode: 200,
|
|
320
|
+
on: (evt, handler) => {
|
|
321
|
+
if (evt === 'data') handler(body);
|
|
322
|
+
if (evt === 'end') handler();
|
|
323
|
+
return mockRes;
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
callback(mockRes);
|
|
327
|
+
return { on: () => undefined, destroy: () => undefined };
|
|
328
|
+
};
|
|
329
|
+
try {
|
|
330
|
+
const m = require('../check-version');
|
|
331
|
+
return fn(m, cpPath);
|
|
332
|
+
} finally {
|
|
333
|
+
https.get = saved;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
it('rejects null version from registry — cache NOT written', async () => {
|
|
338
|
+
await withMockedHttpsBox(null, async (m, cpPath) => {
|
|
339
|
+
const r = await m.getRemoteVersion('@test/pkg');
|
|
340
|
+
// null latest is rejected by getRemoteVersion itself (typeof !== 'string')
|
|
341
|
+
// before even reaching writeCache
|
|
342
|
+
expect(r).toBeNull();
|
|
343
|
+
expect(require('fs').existsSync(cpPath)).toBe(false);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('rejects empty version from registry — cache NOT written', async () => {
|
|
348
|
+
await withMockedHttpsBox('', async (m, cpPath) => {
|
|
349
|
+
const r = await m.getRemoteVersion('@test/pkg');
|
|
350
|
+
// getRemoteVersion passes empty string through (it IS a string),
|
|
351
|
+
// but writeCache's semver validation rejects it
|
|
352
|
+
expect(r).not.toBeNull();
|
|
353
|
+
expect(r.latest).toBe('');
|
|
354
|
+
expect(require('fs').existsSync(cpPath)).toBe(false);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('rejects non-semver version from registry — cache NOT written', async () => {
|
|
359
|
+
await withMockedHttpsBox('not-a-version', async (m, cpPath) => {
|
|
360
|
+
const r = await m.getRemoteVersion('@test/pkg');
|
|
361
|
+
// getRemoteVersion passes it through, but writeCache rejects it
|
|
362
|
+
expect(r).not.toBeNull();
|
|
363
|
+
expect(r.latest).toBe('not-a-version');
|
|
364
|
+
expect(require('fs').existsSync(cpPath)).toBe(false);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('accepts valid semver from registry — cache written', async () => {
|
|
369
|
+
await withMockedHttpsBox('0.18.0', async (m, cpPath) => {
|
|
370
|
+
const r = await m.getRemoteVersion('@test/pkg');
|
|
371
|
+
expect(r).not.toBeNull();
|
|
372
|
+
expect(r.latest).toBe('0.18.0');
|
|
373
|
+
expect(require('fs').existsSync(cpPath)).toBe(true);
|
|
374
|
+
const cached = JSON.parse(require('fs').readFileSync(cpPath, 'utf8'));
|
|
375
|
+
expect(cached.version).toBe('0.18.0');
|
|
376
|
+
});
|
|
377
|
+
});
|
|
256
378
|
});
|
|
257
379
|
|
|
258
380
|
// ──────────────────────────────────────────
|
|
@@ -31,6 +31,12 @@ describe('doctor', () => {
|
|
|
31
31
|
originalDetectDepsTools = [...detectDeps.GATE_CLI_TOOLS];
|
|
32
32
|
detectDeps.GATE_CLI_TOOLS.length = 0;
|
|
33
33
|
}
|
|
34
|
+
// Mock checkBash to return success by default (tests override as needed)
|
|
35
|
+
detectDeps.checkBash = () => ({
|
|
36
|
+
ok: true,
|
|
37
|
+
path: 'bash',
|
|
38
|
+
version: '5.1.16',
|
|
39
|
+
});
|
|
34
40
|
});
|
|
35
41
|
|
|
36
42
|
afterAll(() => {
|
|
@@ -217,9 +223,6 @@ describe('doctor', () => {
|
|
|
217
223
|
if (cmd === 'git --version') {
|
|
218
224
|
return 'git version 2.39.0\n';
|
|
219
225
|
}
|
|
220
|
-
if (cmd === 'bash --version') {
|
|
221
|
-
return 'GNU bash, version 5.1.16\n';
|
|
222
|
-
}
|
|
223
226
|
return '';
|
|
224
227
|
};
|
|
225
228
|
|
|
@@ -237,8 +240,6 @@ describe('doctor', () => {
|
|
|
237
240
|
callback(null, 'v20.0.0\n', '');
|
|
238
241
|
} else if (cmd === 'git --version') {
|
|
239
242
|
callback(null, 'git version 2.39.0\n', '');
|
|
240
|
-
} else if (cmd === 'bash --version') {
|
|
241
|
-
callback(null, 'GNU bash, version 5.1.16\n', '');
|
|
242
243
|
} else {
|
|
243
244
|
callback(null, '', '');
|
|
244
245
|
}
|
|
@@ -401,9 +402,6 @@ describe('doctor', () => {
|
|
|
401
402
|
if (cmd === 'git --version') {
|
|
402
403
|
return 'git version 2.39.0\n';
|
|
403
404
|
}
|
|
404
|
-
if (cmd === 'bash --version') {
|
|
405
|
-
return 'GNU bash, version 5.1.16\n';
|
|
406
|
-
}
|
|
407
405
|
return '';
|
|
408
406
|
};
|
|
409
407
|
// Async mock matching execSync above
|
|
@@ -417,8 +415,6 @@ describe('doctor', () => {
|
|
|
417
415
|
callback(null, 'v20.0.0\n', '');
|
|
418
416
|
} else if (cmd === 'git --version') {
|
|
419
417
|
callback(null, 'git version 2.39.0\n', '');
|
|
420
|
-
} else if (cmd === 'bash --version') {
|
|
421
|
-
callback(null, 'GNU bash, version 5.1.16\n', '');
|
|
422
418
|
} else {
|
|
423
419
|
callback(null, '', '');
|
|
424
420
|
}
|
|
@@ -502,9 +498,6 @@ describe('doctor', () => {
|
|
|
502
498
|
if (cmd === 'git --version') {
|
|
503
499
|
return 'git version 2.39.0\n';
|
|
504
500
|
}
|
|
505
|
-
if (cmd === 'bash --version') {
|
|
506
|
-
return 'GNU bash, version 5.1.16\n';
|
|
507
|
-
}
|
|
508
501
|
return '';
|
|
509
502
|
};
|
|
510
503
|
// Async exec mock for execWithTimeout (util.promisify(exec) in doctor.js)
|
|
@@ -518,8 +511,6 @@ describe('doctor', () => {
|
|
|
518
511
|
callback(null, 'v20.0.0\n', '');
|
|
519
512
|
} else if (cmd === 'git --version') {
|
|
520
513
|
callback(null, 'git version 2.39.0\n', '');
|
|
521
|
-
} else if (cmd === 'bash --version') {
|
|
522
|
-
callback(null, 'GNU bash, version 5.1.16\n', '');
|
|
523
514
|
} else {
|
|
524
515
|
callback(null, '', '');
|
|
525
516
|
}
|
|
@@ -569,6 +560,10 @@ describe('doctor', () => {
|
|
|
569
560
|
it('detects missing environment dependencies', async () => {
|
|
570
561
|
setupLocalInstall();
|
|
571
562
|
seedVersionCache();
|
|
563
|
+
// Make checkBash fail for this test (detect-deps module already loaded + mocked in beforeAll)
|
|
564
|
+
const detectDeps = require('../detect-deps.js');
|
|
565
|
+
const origCheckBash = detectDeps.checkBash;
|
|
566
|
+
detectDeps.checkBash = () => ({ ok: false, message: 'Not found' });
|
|
572
567
|
mockExecFail();
|
|
573
568
|
const { doctor } = require('../doctor');
|
|
574
569
|
|
|
@@ -578,6 +573,9 @@ describe('doctor', () => {
|
|
|
578
573
|
expect(logSpy).toHaveBeenCalledWith(
|
|
579
574
|
expect.stringContaining('Not found')
|
|
580
575
|
);
|
|
576
|
+
|
|
577
|
+
// Restore
|
|
578
|
+
detectDeps.checkBash = origCheckBash;
|
|
581
579
|
});
|
|
582
580
|
|
|
583
581
|
// === Issue #186: Version mismatch detection ===
|
|
@@ -201,6 +201,44 @@ describe('gate-audit', () => {
|
|
|
201
201
|
});
|
|
202
202
|
});
|
|
203
203
|
|
|
204
|
+
describe('duration_anomaly handling', () => {
|
|
205
|
+
it('should exclude duration_anomaly entries from avg_ms calculation', () => {
|
|
206
|
+
const repoRoot = join(TEST_DIR, 'anomaly-avg-test');
|
|
207
|
+
// 2 normal entries: 100ms + 200ms → avg 150ms
|
|
208
|
+
appendAuditEntry(makeEntry({ repo_path: repoRoot, gate_id: 'gate-1', duration_ms: 100 }), repoRoot);
|
|
209
|
+
appendAuditEntry(makeEntry({ repo_path: repoRoot, gate_id: 'gate-1', duration_ms: 200 }), repoRoot);
|
|
210
|
+
// 1 anomalous entry: should be excluded from avg
|
|
211
|
+
appendAuditEntry(
|
|
212
|
+
makeEntry({ repo_path: repoRoot, gate_id: 'gate-1', duration_ms: 1784536915891, duration_anomaly: true }),
|
|
213
|
+
repoRoot,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const stats = computeStats(repoRoot);
|
|
217
|
+
const g1 = stats.find(s => s.gate_id === 'gate-1')!;
|
|
218
|
+
// avg should be (100+200)/2 = 150, NOT (100+200+1784536915891)/3
|
|
219
|
+
expect(g1.avg_ms).toBe(150);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should count anomaly entries separately in stats output', () => {
|
|
223
|
+
const repoRoot = join(TEST_DIR, 'anomaly-count-test');
|
|
224
|
+
appendAuditEntry(makeEntry({ repo_path: repoRoot, gate_id: 'gate-5', duration_ms: 500 }), repoRoot);
|
|
225
|
+
appendAuditEntry(
|
|
226
|
+
makeEntry({ repo_path: repoRoot, gate_id: 'gate-5', duration_ms: 9999999999999, duration_anomaly: true }),
|
|
227
|
+
repoRoot,
|
|
228
|
+
);
|
|
229
|
+
appendAuditEntry(
|
|
230
|
+
makeEntry({ repo_path: repoRoot, gate_id: 'gate-5', duration_ms: 8888888888888, duration_anomaly: true }),
|
|
231
|
+
repoRoot,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const stats = computeStats(repoRoot);
|
|
235
|
+
const g5 = stats.find(s => s.gate_id === 'gate-5')!;
|
|
236
|
+
expect(g5.anomaly_count).toBe(2);
|
|
237
|
+
// avg_ms only from the 1 normal entry
|
|
238
|
+
expect(g5.avg_ms).toBe(500);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
204
242
|
describe('CLI helpers', () => {
|
|
205
243
|
it('should parse CLI options and skip dangling flags', () => {
|
|
206
244
|
expect(parseCliOptions(['--gate-id', 'gate-1', '--passed', 'true', '--dangling'])).toEqual({
|