@boyingliu01/xp-gate 0.8.6 → 0.8.8
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/lib/__tests__/doctor.test.js +143 -0
- package/lib/__tests__/shared-paths.test.js +110 -0
- package/lib/doctor.js +66 -3
- package/lib/shared-paths.js +37 -1
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/test-driven-development/SKILL.md +348 -48
- package/plugins/claude-code/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/plugins/opencode/index.ts +9 -8
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/test-driven-development/SKILL.md +348 -48
- package/plugins/opencode/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/skills/test-driven-development/SKILL.md +348 -48
- package/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/plugins/qoder/skills/test-driven-development/SKILL.md +0 -71
|
@@ -446,4 +446,147 @@ describe('doctor', () => {
|
|
|
446
446
|
expect.stringContaining('Not found')
|
|
447
447
|
);
|
|
448
448
|
});
|
|
449
|
+
|
|
450
|
+
// === Issue #186: Version mismatch detection ===
|
|
451
|
+
|
|
452
|
+
it('detects version mismatch when config version differs from package version', async () => {
|
|
453
|
+
setupLocalInstall();
|
|
454
|
+
// Write config with old version
|
|
455
|
+
const cfg = JSON.parse(fs.readFileSync(configFile(), 'utf8'));
|
|
456
|
+
cfg.version = '0.3.1.1';
|
|
457
|
+
fs.writeFileSync(configFile(), JSON.stringify(cfg, null, 2));
|
|
458
|
+
mockExecSuccess();
|
|
459
|
+
const { doctor } = require('../doctor');
|
|
460
|
+
|
|
461
|
+
const result = await doctor([]);
|
|
462
|
+
|
|
463
|
+
expect(result).toBe(1);
|
|
464
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
465
|
+
expect.stringContaining('Version mismatch')
|
|
466
|
+
);
|
|
467
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
468
|
+
expect.stringContaining('0.3.1.1')
|
|
469
|
+
);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('passes version check when config version matches package version', async () => {
|
|
473
|
+
setupLocalInstall();
|
|
474
|
+
// Write config with matching version
|
|
475
|
+
const pkg = JSON.parse(fs.readFileSync(
|
|
476
|
+
path.join(path.dirname(path.dirname(require.resolve('../doctor'))), 'package.json'), 'utf8'
|
|
477
|
+
));
|
|
478
|
+
const cfg = JSON.parse(fs.readFileSync(configFile(), 'utf8'));
|
|
479
|
+
cfg.version = pkg.version;
|
|
480
|
+
fs.writeFileSync(configFile(), JSON.stringify(cfg, null, 2));
|
|
481
|
+
mockExecSuccess();
|
|
482
|
+
const { doctor } = require('../doctor');
|
|
483
|
+
|
|
484
|
+
const result = await doctor([]);
|
|
485
|
+
|
|
486
|
+
expect(result).toBe(0);
|
|
487
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
488
|
+
expect.stringContaining('All checks passed')
|
|
489
|
+
);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('passes version check when config has no version field (legacy)', async () => {
|
|
493
|
+
setupLocalInstall();
|
|
494
|
+
// Config without version field — legacy install, should not fail
|
|
495
|
+
mockExecSuccess();
|
|
496
|
+
const { doctor } = require('../doctor');
|
|
497
|
+
|
|
498
|
+
const result = await doctor([]);
|
|
499
|
+
|
|
500
|
+
expect(result).toBe(0);
|
|
501
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
502
|
+
expect.stringContaining('All checks passed')
|
|
503
|
+
);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// === Issue #188: templateDir validation ===
|
|
507
|
+
|
|
508
|
+
it('detects templateDir pointing to wrong platform directory', async () => {
|
|
509
|
+
setupLocalInstall();
|
|
510
|
+
// Write config with stale opencode templateDir when qoder is active
|
|
511
|
+
const cfg = JSON.parse(fs.readFileSync(configFile(), 'utf8'));
|
|
512
|
+
cfg.templateDir = path.join(tmpHome, '.config', 'opencode', 'git-hooks-template');
|
|
513
|
+
fs.writeFileSync(configFile(), JSON.stringify(cfg, null, 2));
|
|
514
|
+
|
|
515
|
+
// Create qoder marker to simulate qoder environment
|
|
516
|
+
fs.mkdirSync(path.join(tmpHome, '.qoder', 'skills'), { recursive: true });
|
|
517
|
+
|
|
518
|
+
// Need fresh module because shared-paths caches detectPlatform at module load
|
|
519
|
+
delete require.cache[require.resolve('../shared-paths')];
|
|
520
|
+
delete require.cache[require.resolve('../doctor')];
|
|
521
|
+
const { doctor: doc2 } = require('../doctor');
|
|
522
|
+
mockExecSuccess();
|
|
523
|
+
|
|
524
|
+
const result = await doc2([]);
|
|
525
|
+
|
|
526
|
+
expect(result).toBe(1);
|
|
527
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
528
|
+
expect.stringContaining('templateDir')
|
|
529
|
+
);
|
|
530
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
531
|
+
expect.stringContaining('opencode')
|
|
532
|
+
);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('passes templateDir check when templateDir matches current platform', async () => {
|
|
536
|
+
setupLocalInstall();
|
|
537
|
+
// Write config with correct qoder templateDir
|
|
538
|
+
const cfg = JSON.parse(fs.readFileSync(configFile(), 'utf8'));
|
|
539
|
+
cfg.templateDir = path.join(tmpHome, '.qoder', 'git-hooks-template');
|
|
540
|
+
fs.writeFileSync(configFile(), JSON.stringify(cfg, null, 2));
|
|
541
|
+
|
|
542
|
+
// Create qoder marker
|
|
543
|
+
fs.mkdirSync(path.join(tmpHome, '.qoder', 'skills'), { recursive: true });
|
|
544
|
+
|
|
545
|
+
delete require.cache[require.resolve('../shared-paths')];
|
|
546
|
+
delete require.cache[require.resolve('../doctor')];
|
|
547
|
+
const { doctor: doc2 } = require('../doctor');
|
|
548
|
+
mockExecSuccess();
|
|
549
|
+
|
|
550
|
+
const result = await doc2([]);
|
|
551
|
+
|
|
552
|
+
expect(result).toBe(0);
|
|
553
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
554
|
+
expect.stringContaining('All checks passed')
|
|
555
|
+
);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('passes templateDir check when config has no templateDir field (legacy)', async () => {
|
|
559
|
+
setupLocalInstall();
|
|
560
|
+
// Config without templateDir — legacy install, should not fail
|
|
561
|
+
mockExecSuccess();
|
|
562
|
+
const { doctor } = require('../doctor');
|
|
563
|
+
|
|
564
|
+
const result = await doctor([]);
|
|
565
|
+
|
|
566
|
+
expect(result).toBe(0);
|
|
567
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
568
|
+
expect.stringContaining('All checks passed')
|
|
569
|
+
);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// === Issue #186: --fix syncs global hooks from package source ===
|
|
573
|
+
|
|
574
|
+
it('--fix syncs global hooks when they are outdated', async () => {
|
|
575
|
+
setupGlobalInstall();
|
|
576
|
+
// Write an outdated pre-commit hook (different content)
|
|
577
|
+
const oldContent = '#!/bin/bash\n# OpenCode Quality Gates - Pre-Commit Hook - OLD VERSION\n';
|
|
578
|
+
fs.writeFileSync(path.join(globalHooksDir(), 'pre-commit'), oldContent);
|
|
579
|
+
mockExecSuccess();
|
|
580
|
+
const { doctor } = require('../doctor');
|
|
581
|
+
|
|
582
|
+
const result = await doctor(['--fix']);
|
|
583
|
+
|
|
584
|
+
expect(result).toBe(0);
|
|
585
|
+
// Should have restored from package source
|
|
586
|
+
const restoredContent = fs.readFileSync(path.join(globalHooksDir(), 'pre-commit'), 'utf8');
|
|
587
|
+
expect(restoredContent).not.toBe(oldContent);
|
|
588
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
589
|
+
expect.stringContaining('Restored')
|
|
590
|
+
);
|
|
591
|
+
});
|
|
449
592
|
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test shared-paths
|
|
3
|
+
* @intent Verify platform-aware template directory resolution
|
|
4
|
+
* @covers Issue #188 — templateDir pointing to OpenCode residue path
|
|
5
|
+
*/
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
describe('shared-paths', () => {
|
|
11
|
+
let tmpHome;
|
|
12
|
+
let originalHome;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
originalHome = process.env.HOME;
|
|
16
|
+
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'xpgate-sp-'));
|
|
17
|
+
process.env.HOME = tmpHome;
|
|
18
|
+
vi.resetModules();
|
|
19
|
+
delete require.cache[require.resolve('../shared-paths')];
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
process.env.HOME = originalHome;
|
|
24
|
+
if (tmpHome && fs.existsSync(tmpHome)) {
|
|
25
|
+
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function createPlatformMarker(platform) {
|
|
30
|
+
const dirs = {
|
|
31
|
+
opencode: path.join(tmpHome, '.config', 'opencode', 'skills'),
|
|
32
|
+
'claude-code': path.join(tmpHome, '.claude', 'skills'),
|
|
33
|
+
qoder: path.join(tmpHome, '.qoder', 'skills'),
|
|
34
|
+
};
|
|
35
|
+
const dir = dirs[platform];
|
|
36
|
+
if (dir) {
|
|
37
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
it('returns opencode template dir when no platform marker exists (default)', () => {
|
|
42
|
+
const { TEMPLATE_DIR } = require('../shared-paths');
|
|
43
|
+
expect(TEMPLATE_DIR).toBe(path.join(tmpHome, '.config', 'opencode', 'git-hooks-template'));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('returns opencode template dir when opencode platform marker exists', () => {
|
|
47
|
+
createPlatformMarker('opencode');
|
|
48
|
+
const { TEMPLATE_DIR } = require('../shared-paths');
|
|
49
|
+
expect(TEMPLATE_DIR).toBe(path.join(tmpHome, '.config', 'opencode', 'git-hooks-template'));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('returns claude-code template dir when claude-code platform marker exists', () => {
|
|
53
|
+
createPlatformMarker('claude-code');
|
|
54
|
+
const { TEMPLATE_DIR } = require('../shared-paths');
|
|
55
|
+
expect(TEMPLATE_DIR).toBe(path.join(tmpHome, '.claude', 'git-hooks-template'));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('returns qoder template dir when qoder platform marker exists', () => {
|
|
59
|
+
createPlatformMarker('qoder');
|
|
60
|
+
const { TEMPLATE_DIR } = require('../shared-paths');
|
|
61
|
+
expect(TEMPLATE_DIR).toBe(path.join(tmpHome, '.qoder', 'git-hooks-template'));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('qoder marker takes priority over opencode when both exist', () => {
|
|
65
|
+
createPlatformMarker('opencode');
|
|
66
|
+
createPlatformMarker('qoder');
|
|
67
|
+
const { TEMPLATE_DIR } = require('../shared-paths');
|
|
68
|
+
// Qoder should take priority (checked first)
|
|
69
|
+
expect(TEMPLATE_DIR).toBe(path.join(tmpHome, '.qoder', 'git-hooks-template'));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('detectPlatform returns opencode when no marker exists', () => {
|
|
73
|
+
const { detectPlatform } = require('../shared-paths');
|
|
74
|
+
expect(detectPlatform()).toBe('opencode');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('detectPlatform returns qoder when qoder marker exists', () => {
|
|
78
|
+
createPlatformMarker('qoder');
|
|
79
|
+
const { detectPlatform } = require('../shared-paths');
|
|
80
|
+
expect(detectPlatform()).toBe('qoder');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('detectPlatform returns claude-code when claude-code marker exists', () => {
|
|
84
|
+
createPlatformMarker('claude-code');
|
|
85
|
+
const { detectPlatform } = require('../shared-paths');
|
|
86
|
+
expect(detectPlatform()).toBe('claude-code');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('getTemplateDir returns correct path for each platform', () => {
|
|
90
|
+
const { getTemplateDir } = require('../shared-paths');
|
|
91
|
+
|
|
92
|
+
// No marker → opencode
|
|
93
|
+
expect(getTemplateDir()).toBe(path.join(tmpHome, '.config', 'opencode', 'git-hooks-template'));
|
|
94
|
+
|
|
95
|
+
// Qoder marker
|
|
96
|
+
createPlatformMarker('qoder');
|
|
97
|
+
// Need fresh module to re-evaluate
|
|
98
|
+
delete require.cache[require.resolve('../shared-paths')];
|
|
99
|
+
const { getTemplateDir: getTpl2 } = require('../shared-paths');
|
|
100
|
+
expect(getTpl2()).toBe(path.join(tmpHome, '.qoder', 'git-hooks-template'));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('CONFIG_DIR and GLOBAL_HOOKS_DIR remain under xp-gate regardless of platform', () => {
|
|
104
|
+
createPlatformMarker('qoder');
|
|
105
|
+
const { CONFIG_DIR, GLOBAL_HOOKS_DIR, GLOBAL_ADAPTERS_DIR } = require('../shared-paths');
|
|
106
|
+
expect(CONFIG_DIR).toBe(path.join(tmpHome, '.config', 'xp-gate'));
|
|
107
|
+
expect(GLOBAL_HOOKS_DIR).toBe(path.join(tmpHome, '.config', 'xp-gate', 'hooks'));
|
|
108
|
+
expect(GLOBAL_ADAPTERS_DIR).toBe(path.join(tmpHome, '.config', 'xp-gate', 'adapters'));
|
|
109
|
+
});
|
|
110
|
+
});
|
package/lib/doctor.js
CHANGED
|
@@ -7,11 +7,26 @@ const {
|
|
|
7
7
|
CONFIG_FILE,
|
|
8
8
|
GLOBAL_HOOKS_DIR,
|
|
9
9
|
GLOBAL_ADAPTERS_DIR,
|
|
10
|
+
detectPlatform,
|
|
11
|
+
getTemplateDir,
|
|
10
12
|
} = require('./shared-paths.js');
|
|
11
13
|
|
|
12
14
|
// npm package source dir (template hooks/adapters)
|
|
13
15
|
const PKG_DIR = path.dirname(__dirname);
|
|
14
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Read the package version from the installed package.json.
|
|
19
|
+
* @returns {string|null}
|
|
20
|
+
*/
|
|
21
|
+
function getPackageVersion() {
|
|
22
|
+
try {
|
|
23
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_DIR, 'package.json'), 'utf8'));
|
|
24
|
+
return pkg.version || null;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
15
30
|
/**
|
|
16
31
|
* Signature strings used to verify file ownership.
|
|
17
32
|
*/
|
|
@@ -40,6 +55,10 @@ function getConfig() {
|
|
|
40
55
|
}
|
|
41
56
|
}
|
|
42
57
|
|
|
58
|
+
function saveConfig(config) {
|
|
59
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
60
|
+
}
|
|
61
|
+
|
|
43
62
|
function getGitDir() {
|
|
44
63
|
try {
|
|
45
64
|
return execSync('git rev-parse --git-dir', { encoding: 'utf8' }).trim();
|
|
@@ -190,17 +209,43 @@ function diagnose() {
|
|
|
190
209
|
checks.push(...configResult.checks);
|
|
191
210
|
issues += configResult.issues;
|
|
192
211
|
|
|
193
|
-
// --- Check 2:
|
|
212
|
+
// --- Check 2: Version mismatch ---
|
|
213
|
+
const pkgVersion = getPackageVersion();
|
|
214
|
+
const configVersion = config.version;
|
|
215
|
+
if (configVersion && pkgVersion && configVersion !== pkgVersion) {
|
|
216
|
+
checks.push({
|
|
217
|
+
name: 'Version mismatch',
|
|
218
|
+
status: 'FAIL',
|
|
219
|
+
detail: `config: ${configVersion}, package: ${pkgVersion} — run 'xp-gate doctor --fix' to sync`
|
|
220
|
+
});
|
|
221
|
+
issues++;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// --- Check 3: templateDir validation ---
|
|
225
|
+
const configTemplateDir = config.templateDir;
|
|
226
|
+
if (configTemplateDir) {
|
|
227
|
+
const expectedTemplateDir = getTemplateDir();
|
|
228
|
+
if (configTemplateDir !== expectedTemplateDir) {
|
|
229
|
+
checks.push({
|
|
230
|
+
name: 'templateDir',
|
|
231
|
+
status: 'FAIL',
|
|
232
|
+
detail: `points to ${configTemplateDir}, expected ${expectedTemplateDir} for current platform`
|
|
233
|
+
});
|
|
234
|
+
issues++;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// --- Check 4: Hooks files ---
|
|
194
239
|
if (config.mode === 'local') {
|
|
195
240
|
issues += checkLocalHooks(checks);
|
|
196
241
|
} else {
|
|
197
242
|
issues += checkGlobalHooks(checks);
|
|
198
243
|
}
|
|
199
244
|
|
|
200
|
-
// --- Check
|
|
245
|
+
// --- Check 5: Adapters directory ---
|
|
201
246
|
issues += checkAdapters(checks, config.mode, getGitDir());
|
|
202
247
|
|
|
203
|
-
// --- Check
|
|
248
|
+
// --- Check 6: Environment dependencies ---
|
|
204
249
|
checkEnv(checks);
|
|
205
250
|
|
|
206
251
|
return { checks, issues };
|
|
@@ -245,6 +290,24 @@ function fixIssues(checks, config) {
|
|
|
245
290
|
const srcDir = PKG_DIR;
|
|
246
291
|
let fixed = false;
|
|
247
292
|
|
|
293
|
+
// Fix version mismatch — update config version to match package
|
|
294
|
+
const pkgVersion = getPackageVersion();
|
|
295
|
+
if (pkgVersion && config.version !== pkgVersion) {
|
|
296
|
+
config.version = pkgVersion;
|
|
297
|
+
saveConfig(config);
|
|
298
|
+
console.log(` ✓ Updated config version to ${pkgVersion}`);
|
|
299
|
+
fixed = true;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Fix templateDir mismatch
|
|
303
|
+
const expectedTemplateDir = getTemplateDir();
|
|
304
|
+
if (config.templateDir && config.templateDir !== expectedTemplateDir) {
|
|
305
|
+
config.templateDir = expectedTemplateDir;
|
|
306
|
+
saveConfig(config);
|
|
307
|
+
console.log(` ✓ Updated templateDir to ${expectedTemplateDir}`);
|
|
308
|
+
fixed = true;
|
|
309
|
+
}
|
|
310
|
+
|
|
248
311
|
// Fix missing hooks
|
|
249
312
|
if (config.mode === 'local') {
|
|
250
313
|
const gitDir = getGitDir();
|
package/lib/shared-paths.js
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @intent Eliminate duplicate path constants in init.js / uninstall.js / detect-deps.js
|
|
6
6
|
* @covers Issue #107 — duplicate code between init.js and uninstall.js
|
|
7
|
+
* @covers Issue #188 — templateDir pointing to OpenCode residue path
|
|
7
8
|
*/
|
|
8
9
|
const path = require('path');
|
|
9
10
|
const os = require('os');
|
|
11
|
+
const fs = require('fs');
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Resolve the user's home directory cross-platform.
|
|
@@ -16,10 +18,42 @@ const HOME_DIR = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
|
16
18
|
|
|
17
19
|
const CONFIG_DIR = path.join(HOME_DIR, '.config', 'xp-gate');
|
|
18
20
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'xp-gate.json');
|
|
19
|
-
const TEMPLATE_DIR = path.join(HOME_DIR, '.config', 'opencode', 'git-hooks-template');
|
|
20
21
|
const GLOBAL_HOOKS_DIR = path.join(CONFIG_DIR, 'hooks');
|
|
21
22
|
const GLOBAL_ADAPTERS_DIR = path.join(CONFIG_DIR, 'adapters');
|
|
22
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Detect which AI agent platform is currently in use.
|
|
26
|
+
* Mirrors detectPlatform() in detect-deps.js to avoid circular deps.
|
|
27
|
+
* @returns {'opencode' | 'claude-code' | 'qoder'}
|
|
28
|
+
*/
|
|
29
|
+
function detectPlatform() {
|
|
30
|
+
if (fs.existsSync(path.join(HOME_DIR, '.qoder', 'skills'))) return 'qoder';
|
|
31
|
+
if (fs.existsSync(path.join(HOME_DIR, '.claude', 'skills'))) return 'claude-code';
|
|
32
|
+
return 'opencode';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get the git hooks template directory for the current platform.
|
|
37
|
+
* Each AI agent platform has its own config directory:
|
|
38
|
+
* - opencode: ~/.config/opencode/git-hooks-template
|
|
39
|
+
* - claude-code: ~/.claude/git-hooks-template
|
|
40
|
+
* - qoder: ~/.qoder/git-hooks-template
|
|
41
|
+
*
|
|
42
|
+
* This prevents templateDir from pointing to a stale platform's directory
|
|
43
|
+
* after migrating from one AI agent to another (e.g., OpenCode → Qoder).
|
|
44
|
+
*
|
|
45
|
+
* @returns {string} Path to the platform-specific git hooks template directory
|
|
46
|
+
*/
|
|
47
|
+
function getTemplateDir() {
|
|
48
|
+
const platform = detectPlatform();
|
|
49
|
+
const configDir = platform === 'claude-code' ? '.claude' :
|
|
50
|
+
platform === 'qoder' ? '.qoder' :
|
|
51
|
+
path.join('.config', 'opencode');
|
|
52
|
+
return path.join(HOME_DIR, configDir, 'git-hooks-template');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const TEMPLATE_DIR = getTemplateDir();
|
|
56
|
+
|
|
23
57
|
module.exports = {
|
|
24
58
|
HOME_DIR,
|
|
25
59
|
CONFIG_DIR,
|
|
@@ -27,4 +61,6 @@ module.exports = {
|
|
|
27
61
|
TEMPLATE_DIR,
|
|
28
62
|
GLOBAL_HOOKS_DIR,
|
|
29
63
|
GLOBAL_ADAPTERS_DIR,
|
|
64
|
+
detectPlatform,
|
|
65
|
+
getTemplateDir,
|
|
30
66
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xp-gate",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 6 quality gates, Sprint Flow, and Delphi multi-expert review.",
|
|
6
6
|
"author": {
|