@boyingliu01/xp-gate 0.10.11 → 0.10.13
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__/check-version.test.js +19 -5
- package/lib/__tests__/doctor.test.js +155 -0
- package/lib/__tests__/install-skill.test.js +1 -1
- package/lib/doctor.js +126 -0
- package/lib/init.js +12 -0
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/package.json +1 -1
- 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/sprint-flow/SKILL.md +6 -9
- package/plugins/claude-code/skills/sprint-flow/references/orchestration-rules.md +8 -6
- package/plugins/claude-code/skills/sprint-flow/references/phase-1-plan.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/references/phase-3-review.md +3 -1
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/opencode/__tests__/tui-plugin.test.ts +121 -1
- package/plugins/opencode/__tests__/xp-gate-e2e-upgrade.test.ts +562 -0
- package/plugins/opencode/index.ts +2 -0
- 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/sprint-flow/SKILL.md +6 -9
- package/plugins/opencode/skills/sprint-flow/references/orchestration-rules.md +8 -6
- package/plugins/opencode/skills/sprint-flow/references/phase-1-plan.md +2 -2
- package/plugins/opencode/skills/sprint-flow/references/phase-3-review.md +3 -1
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/opencode/tui-plugin.ts +24 -7
- 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/sprint-flow/SKILL.md +191 -1088
- package/plugins/qoder/skills/sprint-flow/references/orchestration-rules.md +365 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-1-plan.md +2 -2
- package/plugins/qoder/skills/sprint-flow/references/phase-2-build.md +25 -180
- package/plugins/qoder/skills/sprint-flow/references/phase-3-review.md +3 -1
- package/plugins/qoder/skills/sprint-flow/references/phase-6-ship.md +4 -188
- package/plugins/qoder/skills/sprint-flow/references/phase-7-land.md +4 -135
- package/plugins/qoder/skills/sprint-flow/references/phase-8-cleanup.md +4 -187
- package/plugins/qoder/skills/sprint-flow/references/phase-minus-0-5-auto-estimate.md +6 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-minus-1-isolate.md +58 -0
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/sprint-flow/SKILL.md +6 -9
- package/skills/sprint-flow/references/orchestration-rules.md +8 -6
- package/skills/sprint-flow/references/phase-1-plan.md +2 -2
- package/skills/sprint-flow/references/phase-3-review.md +3 -1
- package/skills/test-specification-alignment/AGENTS.md +3 -3
|
@@ -180,11 +180,11 @@ describe('check-version.js — REQ-001-01', () => {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
function withMockedHttps(latestVersion, fn) {
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
const cpPath = require('path').join(
|
|
186
|
-
if (
|
|
187
|
-
try {
|
|
183
|
+
const fsm = require('fs');
|
|
184
|
+
const osm = require('os');
|
|
185
|
+
const cpPath = require('path').join(osm.homedir(), '.xp-gate', 'version-cache.json');
|
|
186
|
+
if (fsm.existsSync(cpPath)) {
|
|
187
|
+
try { fsm.unlinkSync(cpPath); } catch { }
|
|
188
188
|
}
|
|
189
189
|
evictCache();
|
|
190
190
|
const https = require('https');
|
|
@@ -204,11 +204,25 @@ describe('check-version.js — REQ-001-01', () => {
|
|
|
204
204
|
callback(mockRes);
|
|
205
205
|
return { on: () => undefined, destroy: () => undefined };
|
|
206
206
|
};
|
|
207
|
+
// Mock getLocalVersion to return a fixed version so tests don't
|
|
208
|
+
// depend on the real package.json version (which drifts over time).
|
|
209
|
+
const savedReadFileSync = fsm.readFileSync;
|
|
210
|
+
const pkgJsonDir = require('path').join(
|
|
211
|
+
require('path').dirname(require.resolve('../check-version')),
|
|
212
|
+
'..'
|
|
213
|
+
);
|
|
214
|
+
fsm.readFileSync = (...args) => {
|
|
215
|
+
if (String(args[0]).endsWith('package.json') && String(args[0]) === require('path').join(pkgJsonDir, 'package.json')) {
|
|
216
|
+
return JSON.stringify({ name: '@boyingliu01/xp-gate', version: '0.8.21' });
|
|
217
|
+
}
|
|
218
|
+
return savedReadFileSync.apply(fsm, args);
|
|
219
|
+
};
|
|
207
220
|
try {
|
|
208
221
|
const m = require('../check-version');
|
|
209
222
|
return fn(m);
|
|
210
223
|
} finally {
|
|
211
224
|
https.get = saved;
|
|
225
|
+
fsm.readFileSync = savedReadFileSync;
|
|
212
226
|
}
|
|
213
227
|
}
|
|
214
228
|
|
|
@@ -102,6 +102,15 @@ describe('doctor', () => {
|
|
|
102
102
|
);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
function tuiJsonPath() {
|
|
106
|
+
return path.join(tmpHome, '.config', 'opencode', 'tui.json');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function ensureTuiRegistered() {
|
|
110
|
+
fs.mkdirSync(path.dirname(tuiJsonPath()), { recursive: true });
|
|
111
|
+
fs.writeFileSync(tuiJsonPath(), JSON.stringify({ plugin: ['@boyingliu01/opencode-plugin/tui'] }, null, 2));
|
|
112
|
+
}
|
|
113
|
+
|
|
105
114
|
function setupLocalInstall() {
|
|
106
115
|
createXpGatePreCommit(projectHooksDir());
|
|
107
116
|
createXpGatePrePush(projectHooksDir());
|
|
@@ -183,6 +192,7 @@ describe('doctor', () => {
|
|
|
183
192
|
|
|
184
193
|
it('AC-05: doctor reports all checks passed for healthy local install', async () => {
|
|
185
194
|
setupLocalInstall();
|
|
195
|
+
ensureTuiRegistered();
|
|
186
196
|
seedVersionCache();
|
|
187
197
|
mockExecSuccess();
|
|
188
198
|
const { doctor } = require('../doctor');
|
|
@@ -198,6 +208,7 @@ describe('doctor', () => {
|
|
|
198
208
|
|
|
199
209
|
it('AC-05: doctor reports all checks passed for healthy global install', async () => {
|
|
200
210
|
setupGlobalInstall();
|
|
211
|
+
ensureTuiRegistered();
|
|
201
212
|
seedVersionCache();
|
|
202
213
|
mockExecSuccess();
|
|
203
214
|
const { doctor } = require('../doctor');
|
|
@@ -352,6 +363,7 @@ describe('doctor', () => {
|
|
|
352
363
|
|
|
353
364
|
it('AC-10: doctor --fix corrects core.hooksPath in global mode', async () => {
|
|
354
365
|
setupGlobalInstall();
|
|
366
|
+
ensureTuiRegistered();
|
|
355
367
|
seedVersionCache();
|
|
356
368
|
// Mock hooksPath pointing somewhere else
|
|
357
369
|
execSpy = vi.spyOn(childProcess, 'execSync').mockImplementation((cmd) => {
|
|
@@ -455,6 +467,7 @@ describe('doctor', () => {
|
|
|
455
467
|
|
|
456
468
|
it('passes version check when config version matches package version', async () => {
|
|
457
469
|
setupLocalInstall();
|
|
470
|
+
ensureTuiRegistered();
|
|
458
471
|
seedVersionCache();
|
|
459
472
|
// Write config with matching version
|
|
460
473
|
const pkg = JSON.parse(fs.readFileSync(
|
|
@@ -476,6 +489,7 @@ describe('doctor', () => {
|
|
|
476
489
|
|
|
477
490
|
it('passes version check when config has no version field (legacy)', async () => {
|
|
478
491
|
setupLocalInstall();
|
|
492
|
+
ensureTuiRegistered();
|
|
479
493
|
seedVersionCache();
|
|
480
494
|
// Config without version field — legacy install, should not fail
|
|
481
495
|
mockExecSuccess();
|
|
@@ -521,6 +535,7 @@ describe('doctor', () => {
|
|
|
521
535
|
|
|
522
536
|
it('passes templateDir check when templateDir matches current platform', async () => {
|
|
523
537
|
setupLocalInstall();
|
|
538
|
+
ensureTuiRegistered();
|
|
524
539
|
seedVersionCache();
|
|
525
540
|
// Write config with correct qoder templateDir
|
|
526
541
|
const cfg = JSON.parse(fs.readFileSync(configFile(), 'utf8'));
|
|
@@ -546,6 +561,7 @@ describe('doctor', () => {
|
|
|
546
561
|
|
|
547
562
|
it('passes templateDir check when config has no templateDir field (legacy)', async () => {
|
|
548
563
|
setupLocalInstall();
|
|
564
|
+
ensureTuiRegistered();
|
|
549
565
|
seedVersionCache();
|
|
550
566
|
// Config without templateDir — legacy install, should not fail
|
|
551
567
|
mockExecSuccess();
|
|
@@ -563,6 +579,7 @@ describe('doctor', () => {
|
|
|
563
579
|
|
|
564
580
|
it('AC-004-01: doctor shows upgrade prompt at end when outdated', async () => {
|
|
565
581
|
setupLocalInstall();
|
|
582
|
+
ensureTuiRegistered();
|
|
566
583
|
mockExecSuccess();
|
|
567
584
|
// Remove version cache so checkUpgrade hits the real npm registry
|
|
568
585
|
const cachePath = path.join(tmpHome, '.xp-gate', 'version-cache.json');
|
|
@@ -582,6 +599,7 @@ describe('doctor', () => {
|
|
|
582
599
|
|
|
583
600
|
it('AC-004-02: doctor does NOT show upgrade prompt when up to date', async () => {
|
|
584
601
|
setupLocalInstall();
|
|
602
|
+
ensureTuiRegistered();
|
|
585
603
|
mockExecSuccess();
|
|
586
604
|
// Write a cache with a future version so checkUpgrade says "not outdated"
|
|
587
605
|
const cachePath = path.join(tmpHome, '.xp-gate', 'version-cache.json');
|
|
@@ -606,6 +624,7 @@ describe('doctor', () => {
|
|
|
606
624
|
|
|
607
625
|
it('AC-004-03: doctor does NOT fail when version check throws', async () => {
|
|
608
626
|
setupLocalInstall();
|
|
627
|
+
ensureTuiRegistered();
|
|
609
628
|
seedVersionCache();
|
|
610
629
|
mockExecSuccess();
|
|
611
630
|
// Write corrupt cache to trigger checkUpgrade error path
|
|
@@ -642,4 +661,140 @@ describe('doctor', () => {
|
|
|
642
661
|
expect.stringContaining('Restored')
|
|
643
662
|
);
|
|
644
663
|
});
|
|
664
|
+
|
|
665
|
+
// === Check 9: TUI auto-registration (tui.json) ===
|
|
666
|
+
|
|
667
|
+
it('Check 9: PASS when tui.json has @boyingliu01/opencode-plugin/tui registered', async () => {
|
|
668
|
+
setupLocalInstall();
|
|
669
|
+
seedVersionCache();
|
|
670
|
+
mockExecSuccess();
|
|
671
|
+
// Create tui.json with the plugin registered
|
|
672
|
+
fs.mkdirSync(path.dirname(tuiJsonPath()), { recursive: true });
|
|
673
|
+
fs.writeFileSync(tuiJsonPath(), JSON.stringify({ plugin: ['@boyingliu01/opencode-plugin/tui'] }, null, 2));
|
|
674
|
+
const { doctor } = require('../doctor');
|
|
675
|
+
|
|
676
|
+
const result = await doctor([]);
|
|
677
|
+
|
|
678
|
+
expect(result).toBe(0);
|
|
679
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
680
|
+
expect.stringContaining('@boyingliu01/opencode-plugin/tui registered')
|
|
681
|
+
);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
it('Check 9: FAIL when tui.json is missing', async () => {
|
|
685
|
+
setupLocalInstall();
|
|
686
|
+
seedVersionCache();
|
|
687
|
+
mockExecSuccess();
|
|
688
|
+
// Ensure no tui.json
|
|
689
|
+
if (fs.existsSync(tuiJsonPath())) fs.unlinkSync(tuiJsonPath());
|
|
690
|
+
const { doctor } = require('../doctor');
|
|
691
|
+
|
|
692
|
+
const result = await doctor([]);
|
|
693
|
+
|
|
694
|
+
expect(result).toBe(1);
|
|
695
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
696
|
+
expect.stringContaining('TUI registration: Not registered')
|
|
697
|
+
);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
it('Check 9: FAIL when tui.json exists without @boyingliu01/opencode-plugin/tui entry', async () => {
|
|
701
|
+
setupLocalInstall();
|
|
702
|
+
seedVersionCache();
|
|
703
|
+
mockExecSuccess();
|
|
704
|
+
fs.mkdirSync(path.dirname(tuiJsonPath()), { recursive: true });
|
|
705
|
+
fs.writeFileSync(tuiJsonPath(), JSON.stringify({ plugin: ['some-other-plugin'] }, null, 2));
|
|
706
|
+
const { doctor } = require('../doctor');
|
|
707
|
+
|
|
708
|
+
const result = await doctor([]);
|
|
709
|
+
|
|
710
|
+
expect(result).toBe(1);
|
|
711
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
712
|
+
expect.stringContaining('TUI registration: Not registered')
|
|
713
|
+
);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
it('Check 9: WARN when tui.json exists but has no plugin array', async () => {
|
|
717
|
+
setupLocalInstall();
|
|
718
|
+
seedVersionCache();
|
|
719
|
+
mockExecSuccess();
|
|
720
|
+
fs.mkdirSync(path.dirname(tuiJsonPath()), { recursive: true });
|
|
721
|
+
fs.writeFileSync(tuiJsonPath(), JSON.stringify({ someKey: 'value' }, null, 2));
|
|
722
|
+
const { doctor } = require('../doctor');
|
|
723
|
+
|
|
724
|
+
const result = await doctor([]);
|
|
725
|
+
|
|
726
|
+
expect(result).toBe(1);
|
|
727
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
728
|
+
expect.stringContaining('TUI registration: Not registered')
|
|
729
|
+
);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it('Check 9: FAIL with backup when tui.json is corrupt JSON', async () => {
|
|
733
|
+
setupLocalInstall();
|
|
734
|
+
seedVersionCache();
|
|
735
|
+
mockExecSuccess();
|
|
736
|
+
fs.mkdirSync(path.dirname(tuiJsonPath()), { recursive: true });
|
|
737
|
+
fs.writeFileSync(tuiJsonPath(), 'this is not { valid json');
|
|
738
|
+
const { doctor } = require('../doctor');
|
|
739
|
+
|
|
740
|
+
const result = await doctor([]);
|
|
741
|
+
|
|
742
|
+
expect(result).toBe(1);
|
|
743
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
744
|
+
expect.stringContaining('TUI registration: Corrupt')
|
|
745
|
+
);
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// === --fix TUI registration ===
|
|
749
|
+
|
|
750
|
+
it('--fix: diagnoses TUI not registered then re-diagnoses after fix', async () => {
|
|
751
|
+
setupLocalInstall();
|
|
752
|
+
seedVersionCache();
|
|
753
|
+
mockExecSuccess();
|
|
754
|
+
// No tui.json
|
|
755
|
+
if (fs.existsSync(path.join(tmpHome, '.config', 'opencode'))) {
|
|
756
|
+
fs.rmSync(path.join(tmpHome, '.config', 'opencode'), { recursive: true, force: true });
|
|
757
|
+
}
|
|
758
|
+
const { doctor } = require('../doctor');
|
|
759
|
+
|
|
760
|
+
const result = await doctor(['--fix']);
|
|
761
|
+
|
|
762
|
+
expect(result).toBe(0);
|
|
763
|
+
// After fix, tui.json should exist with the plugin registered
|
|
764
|
+
expect(fs.existsSync(tuiJsonPath())).toBe(true);
|
|
765
|
+
const tui = JSON.parse(fs.readFileSync(tuiJsonPath(), 'utf8'));
|
|
766
|
+
expect(tui.plugin).toContain('@boyingliu01/opencode-plugin/tui');
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
it('--fix: append to existing tui.json plugin array', async () => {
|
|
770
|
+
setupLocalInstall();
|
|
771
|
+
seedVersionCache();
|
|
772
|
+
mockExecSuccess();
|
|
773
|
+
fs.mkdirSync(path.dirname(tuiJsonPath()), { recursive: true });
|
|
774
|
+
fs.writeFileSync(tuiJsonPath(), JSON.stringify({ plugin: ['some-other-plugin'] }, null, 2));
|
|
775
|
+
const { doctor } = require('../doctor');
|
|
776
|
+
|
|
777
|
+
const result = await doctor(['--fix']);
|
|
778
|
+
|
|
779
|
+
expect(result).toBe(0);
|
|
780
|
+
const tui = JSON.parse(fs.readFileSync(tuiJsonPath(), 'utf8'));
|
|
781
|
+
expect(tui.plugin).toContain('some-other-plugin');
|
|
782
|
+
expect(tui.plugin).toContain('@boyingliu01/opencode-plugin/tui');
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it('--fix: idempotent — does not duplicate entry when already registered', async () => {
|
|
786
|
+
setupLocalInstall();
|
|
787
|
+
seedVersionCache();
|
|
788
|
+
mockExecSuccess();
|
|
789
|
+
fs.mkdirSync(path.dirname(tuiJsonPath()), { recursive: true });
|
|
790
|
+
fs.writeFileSync(tuiJsonPath(), JSON.stringify({ plugin: ['@boyingliu01/opencode-plugin/tui'] }, null, 2));
|
|
791
|
+
const { doctor } = require('../doctor');
|
|
792
|
+
|
|
793
|
+
const result = await doctor(['--fix']);
|
|
794
|
+
|
|
795
|
+
expect(result).toBe(0);
|
|
796
|
+
const tui = JSON.parse(fs.readFileSync(tuiJsonPath(), 'utf8'));
|
|
797
|
+
// Should still have exactly one entry
|
|
798
|
+
expect(tui.plugin.filter(p => p === '@boyingliu01/opencode-plugin/tui').length).toBe(1);
|
|
799
|
+
});
|
|
645
800
|
});
|
|
@@ -192,7 +192,7 @@ describe('install-skill', () => {
|
|
|
192
192
|
const result = await installSkill('sprint-flow');
|
|
193
193
|
|
|
194
194
|
expect(result).toBe(1);
|
|
195
|
-
expect(console.error).toHaveBeenCalledWith('Error: sprint-flow is already installed');
|
|
195
|
+
expect(console.error).toHaveBeenCalledWith('Error: sprint-flow is already installed\nUse --force to overwrite');
|
|
196
196
|
});
|
|
197
197
|
|
|
198
198
|
it('backs up and replaces when target exists and force=true', async () => {
|
package/lib/doctor.js
CHANGED
|
@@ -229,6 +229,9 @@ function diagnose() {
|
|
|
229
229
|
// --- Check 6: Environment dependencies ---
|
|
230
230
|
checkEnv(checks);
|
|
231
231
|
|
|
232
|
+
// --- Check 7: TUI auto-registration ---
|
|
233
|
+
issues += diagnoseTuiRegistration(checks);
|
|
234
|
+
|
|
232
235
|
return { checks, issues };
|
|
233
236
|
}
|
|
234
237
|
|
|
@@ -438,6 +441,7 @@ function fixIssues(checks, config) {
|
|
|
438
441
|
fixed = fixHooksByMode(config, srcDir) || fixed;
|
|
439
442
|
fixed = fixGlobalHooksPath(config) || fixed;
|
|
440
443
|
fixed = fixMissingAdapters(config.mode, srcDir, getAdaptersDirByMode(config)) || fixed;
|
|
444
|
+
fixed = fixTuiRegistration() || fixed;
|
|
441
445
|
|
|
442
446
|
if (!fixed) {
|
|
443
447
|
console.log(' No fixable issues found.');
|
|
@@ -499,6 +503,124 @@ function diagnoseOpenCodePlugin(checks) {
|
|
|
499
503
|
return 0;
|
|
500
504
|
}
|
|
501
505
|
|
|
506
|
+
/**
|
|
507
|
+
* TUI registration path and expected plugin entry.
|
|
508
|
+
*/
|
|
509
|
+
const TUI_JSON_PATH = path.join(HOME_DIR, '.config', 'opencode', 'tui.json');
|
|
510
|
+
const TUI_PLUGIN_ENTRY = '@boyingliu01/opencode-plugin/tui';
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Read and parse tui.json, returning { data, error }.
|
|
514
|
+
* data = parsed object on success, null on file missing, undefined on corrupt.
|
|
515
|
+
*/
|
|
516
|
+
function readTuiJson() {
|
|
517
|
+
if (!fs.existsSync(TUI_JSON_PATH)) return { data: null, error: null };
|
|
518
|
+
try {
|
|
519
|
+
const raw = fs.readFileSync(TUI_JSON_PATH, 'utf8');
|
|
520
|
+
const data = JSON.parse(raw);
|
|
521
|
+
return { data, error: null };
|
|
522
|
+
} catch (e) {
|
|
523
|
+
return { data: undefined, error: `Corrupt JSON: ${e.message}` };
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Check 9: TUI auto-registration in ~/.config/opencode/tui.json.
|
|
529
|
+
* @returns {number} issue count
|
|
530
|
+
*/
|
|
531
|
+
function diagnoseTuiRegistration(checks) {
|
|
532
|
+
const { data, error } = readTuiJson();
|
|
533
|
+
|
|
534
|
+
if (error) {
|
|
535
|
+
checks.push({ name: 'TUI registration', status: 'FAIL', detail: error });
|
|
536
|
+
return 1;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (data === null) {
|
|
540
|
+
checks.push({ name: 'TUI registration', status: 'FAIL', detail: 'Not registered' });
|
|
541
|
+
return 1;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const plugins = Array.isArray(data.plugin) ? data.plugin : [];
|
|
545
|
+
if (plugins.includes(TUI_PLUGIN_ENTRY)) {
|
|
546
|
+
checks.push({ name: 'TUI registration', status: 'PASS', detail: `${TUI_PLUGIN_ENTRY} registered` });
|
|
547
|
+
return 0;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
checks.push({ name: 'TUI registration', status: 'FAIL', detail: 'Not registered' });
|
|
551
|
+
return 1;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Fix TUI registration: ensure @boyingliu01/opencode-plugin/tui is in tui.json.
|
|
556
|
+
* Uses atomic write (tmp + renameSync) for JSON safety.
|
|
557
|
+
* On corrupt JSON: backup to .corrupt-{timestamp}.bak then rebuild.
|
|
558
|
+
* Idempotent: skips if already registered.
|
|
559
|
+
* @returns {boolean} Whether a fix was applied
|
|
560
|
+
*/
|
|
561
|
+
function fixTuiRegistration() {
|
|
562
|
+
const { data, error } = readTuiJson();
|
|
563
|
+
|
|
564
|
+
// Corrupt JSON: backup old file, rebuild from scratch
|
|
565
|
+
if (error && data === undefined) {
|
|
566
|
+
const ts = Date.now();
|
|
567
|
+
const backupPath = `${TUI_JSON_PATH}.corrupt-${ts}.bak`;
|
|
568
|
+
try {
|
|
569
|
+
fs.copyFileSync(TUI_JSON_PATH, backupPath);
|
|
570
|
+
console.log(` ⚠ TUI config corrupted — backed up to ${backupPath}`);
|
|
571
|
+
} catch { /* non-critical */ }
|
|
572
|
+
// Rebuild fresh
|
|
573
|
+
const dir = path.dirname(TUI_JSON_PATH);
|
|
574
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
575
|
+
const newConfig = { plugin: [TUI_PLUGIN_ENTRY] };
|
|
576
|
+
const tmpPath = `${TUI_JSON_PATH}.tmp`;
|
|
577
|
+
fs.writeFileSync(tmpPath, JSON.stringify(newConfig, null, 2));
|
|
578
|
+
fs.renameSync(tmpPath, TUI_JSON_PATH);
|
|
579
|
+
console.log(` ✓ Registered ${TUI_PLUGIN_ENTRY} in TUI (rebuilt after corrupt backup)`);
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// File doesn't exist: create it
|
|
584
|
+
if (data === null) {
|
|
585
|
+
const dir = path.dirname(TUI_JSON_PATH);
|
|
586
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
587
|
+
const newConfig = { plugin: [TUI_PLUGIN_ENTRY] };
|
|
588
|
+
const tmpPath = `${TUI_JSON_PATH}.tmp`;
|
|
589
|
+
fs.writeFileSync(tmpPath, JSON.stringify(newConfig, null, 2));
|
|
590
|
+
fs.renameSync(tmpPath, TUI_JSON_PATH);
|
|
591
|
+
console.log(` ✓ Created TUI config with ${TUI_PLUGIN_ENTRY}`);
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// File exists, check if plugin already registered (idempotent)
|
|
596
|
+
const plugins = Array.isArray(data.plugin) ? data.plugin : [];
|
|
597
|
+
if (plugins.includes(TUI_PLUGIN_ENTRY)) return false;
|
|
598
|
+
|
|
599
|
+
// Append plugin entry
|
|
600
|
+
data.plugin = plugins.concat([TUI_PLUGIN_ENTRY]);
|
|
601
|
+
const tmpPath = `${TUI_JSON_PATH}.tmp`;
|
|
602
|
+
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
|
|
603
|
+
fs.renameSync(tmpPath, TUI_JSON_PATH);
|
|
604
|
+
console.log(` ✓ Added ${TUI_PLUGIN_ENTRY} to TUI config`);
|
|
605
|
+
return true;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Wrapper for init.js: ensure TUI is registered without console output.
|
|
610
|
+
* Returns true if the plugin is already registered or was just added.
|
|
611
|
+
*/
|
|
612
|
+
function ensureTuiRegistration() {
|
|
613
|
+
const { data } = readTuiJson();
|
|
614
|
+
if (data === null || data === undefined) {
|
|
615
|
+
fixTuiRegistration();
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
const plugins = Array.isArray(data.plugin) ? data.plugin : [];
|
|
619
|
+
if (!plugins.includes(TUI_PLUGIN_ENTRY)) {
|
|
620
|
+
fixTuiRegistration();
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
502
624
|
async function doctor(args) {
|
|
503
625
|
const fixMode = args.includes('--fix');
|
|
504
626
|
|
|
@@ -551,4 +673,8 @@ module.exports = {
|
|
|
551
673
|
fixMissingHooks,
|
|
552
674
|
fixCoreHooksPath,
|
|
553
675
|
fixMissingAdapters,
|
|
676
|
+
diagnoseTuiRegistration,
|
|
677
|
+
fixTuiRegistration,
|
|
678
|
+
ensureTuiRegistration,
|
|
679
|
+
readTuiJson,
|
|
554
680
|
};
|
package/lib/init.js
CHANGED
|
@@ -340,6 +340,12 @@ async function installLocal(args) {
|
|
|
340
340
|
injectKarpathyPrinciples(projectRoot);
|
|
341
341
|
configureOpenCodePlugin(srcDir, projectRoot);
|
|
342
342
|
|
|
343
|
+
// Auto-register TUI plugin globally (idempotent)
|
|
344
|
+
try {
|
|
345
|
+
const { ensureTuiRegistration } = require('../lib/doctor.js');
|
|
346
|
+
ensureTuiRegistration();
|
|
347
|
+
} catch { /* non-critical: TUI registration is best-effort */ }
|
|
348
|
+
|
|
343
349
|
console.log('\nInstallation complete!');
|
|
344
350
|
console.log('Run git commit to trigger quality gates');
|
|
345
351
|
return 0;
|
|
@@ -390,6 +396,12 @@ async function setupGlobal(args) {
|
|
|
390
396
|
const manifest = generateGlobalManifest(srcDir);
|
|
391
397
|
updateConfig({ lastInit: new Date().toISOString(), mode: 'global', templateDir: TEMPLATE_DIR, manifest });
|
|
392
398
|
|
|
399
|
+
// Auto-register TUI plugin globally (idempotent)
|
|
400
|
+
try {
|
|
401
|
+
const { ensureTuiRegistration } = require('../lib/doctor.js');
|
|
402
|
+
ensureTuiRegistration();
|
|
403
|
+
} catch { /* non-critical: TUI registration is best-effort */ }
|
|
404
|
+
|
|
393
405
|
console.log('\nGlobal setup complete!');
|
|
394
406
|
console.log('All git repositories will now use xp-gate quality gates.');
|
|
395
407
|
console.log('Per-project adapters can still override by creating <repo>/githooks/');
|
package/mock-policy/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MOCK-POLICY KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-06-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-06-24
|
|
4
|
+
**Commit:** 5019773
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.10.
|
|
6
|
+
**Version:** 0.10.13.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-06-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-06-24
|
|
4
|
+
**Commit:** 5019773
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.10.
|
|
6
|
+
**Version:** 0.10.13.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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xp-gate",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.13",
|
|
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-06-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-06-24
|
|
4
|
+
**Commit:** 5019773
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.10.
|
|
6
|
+
**Version:** 0.10.13.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-06-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-06-24
|
|
4
|
+
**Commit:** 5019773
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.10.
|
|
6
|
+
**Version:** 0.10.13.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**11-phase** development pipeline: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → USER ACCEPTANCE → FEEDBACK → SHIP → LAND → CLEANUP. Phase 2 default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE in Phase 1: design must pass Delphi review (≥90% consensus) before any coding.
|
|
@@ -237,11 +237,11 @@ Sprint Flow: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW
|
|
|
237
237
|
- **HARD-GATE**: 设计未批准 → 不可进入实现
|
|
238
238
|
|
|
239
239
|
### Phase 1: PLAN(共识评审)
|
|
240
|
-
- **注意**: `autoplan` 和 `to-issues`
|
|
241
|
-
-
|
|
240
|
+
- **注意**: `autoplan`、`delphi-review` 和 `to-issues` 均为交互式 skill,**必须由 orchestrator 直接执行**(Issue #225, #248, #249)
|
|
241
|
+
- **执行模式(三阶段,全部 orchestrator 直接执行)**:
|
|
242
242
|
1. **Orchestrator 直接执行 autoplan**: `skill(name="autoplan")` → 用户确认 taste_decisions
|
|
243
|
-
2. **Orchestrator 直接执行
|
|
244
|
-
3. **
|
|
243
|
+
2. **Orchestrator 直接执行 delphi-review**: `skill(name="delphi-review")` — 等待 APPROVED(非 APPROVED 时需等待用户确认/处理)
|
|
244
|
+
3. **Orchestrator 直接执行 to-issues**: `skill(name="to-issues")` → 用户确认 Issue 拆分方案
|
|
245
245
|
- 输入: phase-0-summary.md + 设计文档
|
|
246
246
|
- 输出: `specification.yaml`(含 user_stories[])+ `slices-manifest.json`
|
|
247
247
|
|
|
@@ -276,13 +276,10 @@ Sprint Flow: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW
|
|
|
276
276
|
5. **Mock Minimization**: integration-first, mock 仅限 external services, 密度 > 30% 需 `@mock-justified`
|
|
277
277
|
|
|
278
278
|
### Phase 3: REVIEW + TEST(验证)
|
|
279
|
-
- **
|
|
279
|
+
- **Orchestrator 直接执行**: delphi-review code-walkthrough 需要用户确认 verdict(Issue #249),**必须由 orchestrator 直接调用** `skill(name="delphi-review")`
|
|
280
|
+
- **Orchestrator 按序执行**: `delphi-review --mode code-walkthrough` → 等待 APPROVED → `test-specification-alignment` → `browse` (gstack) → 可选 `qa`/`design-review`/`benchmark` (gstack)
|
|
280
281
|
- 输入: phase-2-summary.md + MVP 代码
|
|
281
282
|
- 输出: 评审报告 + 测试对齐结果
|
|
282
|
-
- `delphi-review --mode code-walkthrough` — 多专家匿名代码走查
|
|
283
|
-
- `test-specification-alignment` — 测试与 Spec 对齐验证
|
|
284
|
-
- `browse` (gstack) — 浏览器自动化测试
|
|
285
|
-
- `k6` / `locust` / `gatling` — 负载/压力测试(可选,后端项目)
|
|
286
283
|
|
|
287
284
|
### 负载/压力测试(可选)
|
|
288
285
|
- **适用项目**:主要用于后端服务的压力测试 (k6/Locust/Gatling),Web 前端已有 `benchmark` 技能覆盖 Core Web Vitals、加载时间和资源大小等性能指标
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
| -1 | ISOLATE | ❌ | Bash(直接执行) | 无 | orchestrator |
|
|
32
32
|
| -0.5 | AUTO-ESTIMATE | ❌ | Bash(直接执行) | 无 | orchestrator |
|
|
33
33
|
| 0 | THINK | ❌ | orchestrator(直接执行) | `["brainstorming"]` | orchestrator |
|
|
34
|
-
| 1 | PLAN |
|
|
34
|
+
| 1 | PLAN | ❌ | orchestrator(直接执行) | `["autoplan", "delphi-review", "to-issues"]` | orchestrator |
|
|
35
35
|
| 2 | BUILD | ✅ | ralph-loop | `["test-driven-development"]` | subagent |
|
|
36
|
-
| 3 | REVIEW |
|
|
36
|
+
| 3 | REVIEW | ❌ | orchestrator(直接执行) | `["delphi-review", "test-specification-alignment"]` | orchestrator |
|
|
37
37
|
| 4 | USER ACCEPT | ❌ | **强制人工** | 无 | 用户 |
|
|
38
38
|
| 5 | FEEDBACK | ✅ | `quick` | `["learn", "retro", "systematic-debugging"]` | subagent |
|
|
39
39
|
| 6 | SHIP | ❌ | orchestrator(直接执行) | `["finishing-a-development-branch", "ship"]` | orchestrator |
|
|
@@ -46,15 +46,17 @@
|
|
|
46
46
|
|-------|-------|---------------------------|
|
|
47
47
|
| 0 | `brainstorming` | 需要与用户对话确认需求、提出澄清问题。Subagent 是 fire-and-forget 模式,无法暂停等待用户输入(Issue #217) |
|
|
48
48
|
| 1 | `autoplan` | taste_decisions 节点暂停等待用户确认。必须由 orchestrator 直接执行(Issue #225) |
|
|
49
|
+
| 1 | `delphi-review` | design 模式需等待 verdict APPROVED;非 APPROVED 时需用户确认是否接受分歧方案(Issue #249) |
|
|
49
50
|
| 1 | `to-issues` | Step 6 "向用户确认" — 展示拆分结果,等待用户批准后才生成 slices-manifest.json |
|
|
51
|
+
| 3 | `delphi-review --mode code-walkthrough` | Code walkthrough 非 APPROVED 时需暂停等待用户处理 Critical Issues(Issue #249) |
|
|
50
52
|
| 6 | `finishing-a-development-branch` | 4 选项菜单 (merge/PR/keep/discard) 需要用户选择;Option 4 (discard) 要求 typed confirmation |
|
|
51
53
|
| 6 | `ship` | PR 创建前需要用户确认;包含 AskUserQuestion STOP 点 |
|
|
52
54
|
| 7 | `land-and-deploy` | Merge 确认、rollback 决策 — 均为用户交互点 |
|
|
53
55
|
|
|
54
|
-
**Phase 1
|
|
56
|
+
**Phase 1 执行模式(全部 orchestrator 直接执行)**:
|
|
55
57
|
1. **Orchestrator 直接执行 autoplan**:`skill(name="autoplan")` → 等待用户确认 taste_decisions
|
|
56
|
-
2. **Orchestrator 直接执行
|
|
57
|
-
3. **
|
|
58
|
+
2. **Orchestrator 直接执行 delphi-review**:`skill(name="delphi-review")` → 等待 APPROVED(非 APPROVED 时暂停等待用户确认)
|
|
59
|
+
3. **Orchestrator 直接执行 to-issues**:`skill(name="to-issues")` → 等待用户确认 Issue 拆分
|
|
58
60
|
|
|
59
61
|
**上下文隔离原则**:
|
|
60
62
|
- 每个 Subagent 在**独立 session** 中启动,不继承 orchestrator 的对话历史
|
|
@@ -71,7 +73,7 @@
|
|
|
71
73
|
| Phase 0 | phase--1-summary(仅路径) | 隔离环境信息(worktree 路径) |
|
|
72
74
|
| Phase 1 | phase-0-summary.md + design-doc | 设计决策 + 结构化规格 |
|
|
73
75
|
| Phase 2 | phase-1-summary.md + specification.yaml | 评审结论 + REQ 列表 |
|
|
74
|
-
| Phase 3 | phase-2-summary.md + MVP 代码 |
|
|
76
|
+
| Phase 3 | phase-2-summary.md + MVP 代码 | 构建结果(orchestrator 直接执行,无 subagent 上下文继承) |
|
|
75
77
|
| Phase 4 | — | **人工验收**。Phase 4 不产生 subagent summary,但用户验收结果记录在 `.sprint-state/phase-outputs/emergent-issues.md`(如有 emergent issues)。Phase 5 加载此文件。 |
|
|
76
78
|
| Phase 5 | phase-4-summary.md + emergent-issues.md | 验收结论 |
|
|
77
79
|
| Phase 6 | phase-5-summary.md + feedback-log.md | 复盘结论 |
|
|
@@ -105,7 +105,7 @@ Decision 2: [决策描述]
|
|
|
105
105
|
|
|
106
106
|
---
|
|
107
107
|
|
|
108
|
-
### Step 2b: 调用 delphi-review
|
|
108
|
+
### Step 2b: 调用 delphi-review(强制,orchestrator 直接执行)
|
|
109
109
|
|
|
110
110
|
```bash
|
|
111
111
|
skill(name="delphi-review", user_message="[设计文档 + taste_decisions 确认结果]")
|
|
@@ -117,7 +117,7 @@ delphi-review 执行:
|
|
|
117
117
|
- 输出: APPROVED / REQUEST_CHANGES
|
|
118
118
|
|
|
119
119
|
**如果 REQUEST_CHANGES**:
|
|
120
|
-
- ⚠️
|
|
120
|
+
- ⚠️ 暂停等待用户处理(orchestrator 直接执行,可交互确认)
|
|
121
121
|
- 修复后重新评审(从 Round 2 起步)
|
|
122
122
|
- 直到 APPROVED
|
|
123
123
|
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
多专家代码走查、测试对齐、浏览器测试。确保 MVP 符合 specification。
|
|
6
6
|
|
|
7
|
+
**执行者**: orchestrator 直接执行全部步骤(非 subagent dispatch)— Issue #249。
|
|
8
|
+
|
|
7
9
|
Web 前端项目额外增加:系统化 QA、视觉审计、性能基线。
|
|
8
10
|
|
|
9
11
|
---
|
|
@@ -46,7 +48,7 @@ delphi code-walkthrough 执行:
|
|
|
46
48
|
- ≥90% 共识 + APPROVED 才通过
|
|
47
49
|
|
|
48
50
|
**如果 REQUEST_CHANGES**:
|
|
49
|
-
- ⚠️
|
|
51
|
+
- ⚠️ 暂停等待用户处理 Critical Issues + Major Concerns(orchestrator 直接执行,可交互)
|
|
50
52
|
- 修复后回到 Round 2 重新评审
|
|
51
53
|
|
|
52
54
|
**如果 APPROVED**:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-06-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-06-24
|
|
4
|
+
**Commit:** 5019773
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.10.
|
|
6
|
+
**Version:** 0.10.13.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|