@boyingliu01/xp-gate 0.14.27 → 0.14.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/adapter-common.sh +0 -0
  2. package/adapters/cpp.sh +0 -0
  3. package/adapters/dart.sh +0 -0
  4. package/adapters/flutter.sh +0 -0
  5. package/adapters/go.sh +0 -0
  6. package/adapters/iac.sh +0 -0
  7. package/adapters/java.sh +0 -0
  8. package/adapters/kotlin.sh +0 -0
  9. package/adapters/objectivec.sh +0 -0
  10. package/adapters/plugins/book299-20132-python/scripts/install-gradle.sh +0 -0
  11. package/adapters/plugins/book299-20132-python/scripts/install-maven.sh +0 -0
  12. package/adapters/plugins/book299-4081-c/scripts/install-gradle.sh +0 -0
  13. package/adapters/plugins/book299-4081-c/scripts/install-maven.sh +0 -0
  14. package/adapters/plugins/book299-4083-javascriptes5/scripts/install-gradle.sh +0 -0
  15. package/adapters/plugins/book299-4083-javascriptes5/scripts/install-maven.sh +0 -0
  16. package/adapters/powershell.sh +0 -0
  17. package/adapters/python.sh +0 -0
  18. package/adapters/shell.sh +0 -0
  19. package/adapters/swift.sh +0 -0
  20. package/adapters/typescript.sh +0 -0
  21. package/bin/xp-gate.js +0 -0
  22. package/gate-3.sh +0 -0
  23. package/gate-4.sh +0 -0
  24. package/gate-7.sh +0 -0
  25. package/gate-8.sh +0 -0
  26. package/gate-9.sh +0 -0
  27. package/hooks/adapter-common.sh +0 -0
  28. package/hooks/gate-3.sh +0 -0
  29. package/hooks/gate-4.sh +0 -0
  30. package/hooks/gate-7.sh +0 -0
  31. package/hooks/gate-8.sh +0 -0
  32. package/hooks/gate-9.sh +0 -0
  33. package/hooks/pre-commit +0 -0
  34. package/hooks/pre-push +0 -0
  35. package/lib/__tests__/phase-transition.test.js +174 -174
  36. package/lib/phase-transition.js +211 -211
  37. package/mock-policy/AGENTS.md +2 -2
  38. package/mutation/AGENTS.md +2 -2
  39. package/package.json +2 -2
  40. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  41. package/plugins/claude-code/bin/delphi-review-guard.sh +0 -0
  42. package/plugins/claude-code/bin/xp-gate-check +0 -0
  43. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  44. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  45. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  46. package/plugins/opencode/package.json +1 -1
  47. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  48. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  49. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  50. package/plugins/qoder/agents/delphi-architecture.md +48 -48
  51. package/plugins/qoder/agents/delphi-feasibility.md +48 -48
  52. package/plugins/qoder/agents/delphi-technical.md +48 -48
  53. package/plugins/qoder/bin/sprint-flow-guard.sh +0 -0
  54. package/plugins/qoder/bin/xp-gate-check +0 -0
  55. package/plugins/qoder/plugin.json +1 -1
  56. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  57. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  58. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  59. package/principles/AGENTS.md +2 -2
  60. package/scripts/delphi-external-review.cjs +432 -432
  61. package/skills/delphi-review/AGENTS.md +2 -2
  62. package/skills/sprint-flow/AGENTS.md +2 -2
  63. package/skills/test-specification-alignment/AGENTS.md +2 -2
package/adapter-common.sh CHANGED
File without changes
package/adapters/cpp.sh CHANGED
File without changes
package/adapters/dart.sh CHANGED
File without changes
File without changes
package/adapters/go.sh CHANGED
File without changes
package/adapters/iac.sh CHANGED
File without changes
package/adapters/java.sh CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
package/adapters/shell.sh CHANGED
File without changes
package/adapters/swift.sh CHANGED
File without changes
File without changes
package/bin/xp-gate.js CHANGED
File without changes
package/gate-3.sh CHANGED
File without changes
package/gate-4.sh CHANGED
File without changes
package/gate-7.sh CHANGED
File without changes
package/gate-8.sh CHANGED
File without changes
package/gate-9.sh CHANGED
File without changes
File without changes
package/hooks/gate-3.sh CHANGED
File without changes
package/hooks/gate-4.sh CHANGED
File without changes
package/hooks/gate-7.sh CHANGED
File without changes
package/hooks/gate-8.sh CHANGED
File without changes
package/hooks/gate-9.sh CHANGED
File without changes
package/hooks/pre-commit CHANGED
File without changes
package/hooks/pre-push CHANGED
File without changes
@@ -1,174 +1,174 @@
1
- /**
2
- * Tests for phase-transition CLI command
3
- * @test REQ-338 Phase transition CLI + dashboard rendering
4
- * @intent Verify phase-transition command updates state and renders dashboard
5
- * @covers AC-338-01 (CLI transitions phase and writes state)
6
- * @covers AC-338-02 (--render flag outputs ASCII dashboard)
7
- * @covers AC-338-03 (invalid inputs return error exit code)
8
- * @covers AC-338-04 (--outputs flag records outputs in state)
9
- */
10
-
11
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
12
- import fs from 'node:fs';
13
- import path from 'node:path';
14
- import os from 'node:os';
15
- import { handlePhaseTransition, renderDashboard } from '../phase-transition.js';
16
-
17
- describe('phase-transition', () => {
18
- let tmpDir;
19
-
20
- beforeEach(() => {
21
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'phase-transition-test-'));
22
- });
23
-
24
- afterEach(() => {
25
- fs.rmSync(tmpDir, { recursive: true, force: true });
26
- });
27
-
28
- describe('handlePhaseTransition()', () => {
29
- it('returns 0 and shows help with --help', async () => {
30
- const code = await handlePhaseTransition(['--help']);
31
- expect(code).toBe(0);
32
- });
33
-
34
- it('returns 1 for missing arguments', async () => {
35
- const code = await handlePhaseTransition([]);
36
- expect(code).toBe(1);
37
- });
38
-
39
- it('returns 1 for invalid phase number', async () => {
40
- const code = await handlePhaseTransition(['7', 'completed']);
41
- expect(code).toBe(1);
42
- });
43
-
44
- it('returns 1 for invalid status', async () => {
45
- const code = await handlePhaseTransition(['1', 'invalid_status']);
46
- expect(code).toBe(1);
47
- });
48
-
49
- it('transitions phase and writes state file', async () => {
50
- const code = await handlePhaseTransition(['1', 'completed', '--dir', tmpDir]);
51
- expect(code).toBe(0);
52
-
53
- const stateFile = path.join(tmpDir, '.sprint-state', 'sprint-state.json');
54
- expect(fs.existsSync(stateFile)).toBe(true);
55
-
56
- const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
57
- expect(state.phase).toBe(1);
58
- expect(state.phase_history).toHaveLength(1);
59
- expect(state.phase_history[0].status).toBe('completed');
60
- expect(state.phase_history[0].phase_name).toBe('PREP');
61
- });
62
-
63
- it('records outputs when --outputs flag is provided', async () => {
64
- const outputs = JSON.stringify({ spec: 'path/to/spec.yaml' });
65
- const code = await handlePhaseTransition(['2', 'completed', '--outputs', outputs, '--dir', tmpDir]);
66
- expect(code).toBe(0);
67
-
68
- const stateFile = path.join(tmpDir, '.sprint-state', 'sprint-state.json');
69
- const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
70
- expect(state.outputs.spec).toBe('path/to/spec.yaml');
71
- });
72
-
73
- it('returns 1 for invalid --outputs JSON', async () => {
74
- const code = await handlePhaseTransition(['1', 'completed', '--outputs', 'not-json', '--dir', tmpDir]);
75
- expect(code).toBe(1);
76
- });
77
-
78
- it('transitions through multiple phases sequentially', async () => {
79
- await handlePhaseTransition(['1', 'completed', '--dir', tmpDir]);
80
- await handlePhaseTransition(['2', 'in_progress', '--dir', tmpDir]);
81
- await handlePhaseTransition(['2', 'completed', '--dir', tmpDir]);
82
- await handlePhaseTransition(['3', 'in_progress', '--dir', tmpDir]);
83
-
84
- const stateFile = path.join(tmpDir, '.sprint-state', 'sprint-state.json');
85
- const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
86
-
87
- expect(state.phase).toBe(3);
88
- expect(state.phase_history).toHaveLength(3);
89
- expect(state.phase_history[0].status).toBe('completed');
90
- expect(state.phase_history[1].status).toBe('completed');
91
- expect(state.phase_history[2].status).toBe('in_progress');
92
- });
93
-
94
- it('updates existing entry when same phase is transitioned again', async () => {
95
- await handlePhaseTransition(['1', 'in_progress', '--dir', tmpDir]);
96
- await handlePhaseTransition(['1', 'completed', '--dir', tmpDir]);
97
-
98
- const stateFile = path.join(tmpDir, '.sprint-state', 'sprint-state.json');
99
- const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
100
-
101
- // Should NOT have duplicate entries
102
- expect(state.phase_history).toHaveLength(1);
103
- expect(state.phase_history[0].status).toBe('completed');
104
- expect(state.phase_history[0].completed_at).toBeDefined();
105
- });
106
- });
107
-
108
- describe('renderDashboard()', () => {
109
- it('returns "No sprint state found" for null state', () => {
110
- const result = renderDashboard(null);
111
- expect(result).toBe('No sprint state found');
112
- });
113
-
114
- it('renders dashboard with all 6 phases', () => {
115
- const state = {
116
- id: 'sprint-test-123',
117
- task_description: 'Test feature',
118
- phase: 3,
119
- status: 'in_progress',
120
- started_at: '2026-07-21T10:00:00Z',
121
- phase_history: [
122
- { phase: 1, phase_name: 'PREP', status: 'completed', duration_seconds: 120 },
123
- { phase: 2, phase_name: 'DESIGN', status: 'completed', duration_seconds: 300 },
124
- { phase: 3, phase_name: 'BUILD', status: 'in_progress' },
125
- ],
126
- isolation: { branch: 'sprint/test-branch' },
127
- outputs: {},
128
- };
129
-
130
- const result = renderDashboard(state);
131
-
132
- expect(result).toContain('SPRINT PROGRESS');
133
- expect(result).toContain('sprint-test-123');
134
- expect(result).toContain('Test feature');
135
- expect(result).toContain('sprint/test-branch');
136
- expect(result).toContain('Phase 1/6');
137
- expect(result).toContain('Phase 6/6');
138
- expect(result).toContain('✅'); // completed phases
139
- expect(result).toContain('🔄'); // in_progress phase
140
- expect(result).toContain('⏳'); // pending phases
141
- });
142
-
143
- it('shows outputs section when outputs exist', () => {
144
- const state = {
145
- id: 'sprint-out',
146
- task_description: 'Feature',
147
- phase: 2,
148
- status: 'in_progress',
149
- started_at: '2026-07-21T10:00:00Z',
150
- phase_history: [],
151
- isolation: { branch: 'test' },
152
- outputs: { spec: 'design.yaml', pr: '#42' },
153
- };
154
-
155
- const result = renderDashboard(state);
156
- expect(result).toContain('输出物');
157
- expect(result).toContain('spec: design.yaml');
158
- expect(result).toContain('pr: #42');
159
- });
160
-
161
- it('handles minimal state gracefully', () => {
162
- const state = {
163
- id: 'sprint-min',
164
- phase: 1,
165
- status: 'in_progress',
166
- phase_history: [],
167
- };
168
-
169
- const result = renderDashboard(state);
170
- expect(result).toContain('SPRINT PROGRESS');
171
- expect(result).toContain('sprint-min');
172
- });
173
- });
174
- });
1
+ /**
2
+ * Tests for phase-transition CLI command
3
+ * @test REQ-338 Phase transition CLI + dashboard rendering
4
+ * @intent Verify phase-transition command updates state and renders dashboard
5
+ * @covers AC-338-01 (CLI transitions phase and writes state)
6
+ * @covers AC-338-02 (--render flag outputs ASCII dashboard)
7
+ * @covers AC-338-03 (invalid inputs return error exit code)
8
+ * @covers AC-338-04 (--outputs flag records outputs in state)
9
+ */
10
+
11
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import os from 'node:os';
15
+ import { handlePhaseTransition, renderDashboard } from '../phase-transition.js';
16
+
17
+ describe('phase-transition', () => {
18
+ let tmpDir;
19
+
20
+ beforeEach(() => {
21
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'phase-transition-test-'));
22
+ });
23
+
24
+ afterEach(() => {
25
+ fs.rmSync(tmpDir, { recursive: true, force: true });
26
+ });
27
+
28
+ describe('handlePhaseTransition()', () => {
29
+ it('returns 0 and shows help with --help', async () => {
30
+ const code = await handlePhaseTransition(['--help']);
31
+ expect(code).toBe(0);
32
+ });
33
+
34
+ it('returns 1 for missing arguments', async () => {
35
+ const code = await handlePhaseTransition([]);
36
+ expect(code).toBe(1);
37
+ });
38
+
39
+ it('returns 1 for invalid phase number', async () => {
40
+ const code = await handlePhaseTransition(['7', 'completed']);
41
+ expect(code).toBe(1);
42
+ });
43
+
44
+ it('returns 1 for invalid status', async () => {
45
+ const code = await handlePhaseTransition(['1', 'invalid_status']);
46
+ expect(code).toBe(1);
47
+ });
48
+
49
+ it('transitions phase and writes state file', async () => {
50
+ const code = await handlePhaseTransition(['1', 'completed', '--dir', tmpDir]);
51
+ expect(code).toBe(0);
52
+
53
+ const stateFile = path.join(tmpDir, '.sprint-state', 'sprint-state.json');
54
+ expect(fs.existsSync(stateFile)).toBe(true);
55
+
56
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
57
+ expect(state.phase).toBe(1);
58
+ expect(state.phase_history).toHaveLength(1);
59
+ expect(state.phase_history[0].status).toBe('completed');
60
+ expect(state.phase_history[0].phase_name).toBe('PREP');
61
+ });
62
+
63
+ it('records outputs when --outputs flag is provided', async () => {
64
+ const outputs = JSON.stringify({ spec: 'path/to/spec.yaml' });
65
+ const code = await handlePhaseTransition(['2', 'completed', '--outputs', outputs, '--dir', tmpDir]);
66
+ expect(code).toBe(0);
67
+
68
+ const stateFile = path.join(tmpDir, '.sprint-state', 'sprint-state.json');
69
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
70
+ expect(state.outputs.spec).toBe('path/to/spec.yaml');
71
+ });
72
+
73
+ it('returns 1 for invalid --outputs JSON', async () => {
74
+ const code = await handlePhaseTransition(['1', 'completed', '--outputs', 'not-json', '--dir', tmpDir]);
75
+ expect(code).toBe(1);
76
+ });
77
+
78
+ it('transitions through multiple phases sequentially', async () => {
79
+ await handlePhaseTransition(['1', 'completed', '--dir', tmpDir]);
80
+ await handlePhaseTransition(['2', 'in_progress', '--dir', tmpDir]);
81
+ await handlePhaseTransition(['2', 'completed', '--dir', tmpDir]);
82
+ await handlePhaseTransition(['3', 'in_progress', '--dir', tmpDir]);
83
+
84
+ const stateFile = path.join(tmpDir, '.sprint-state', 'sprint-state.json');
85
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
86
+
87
+ expect(state.phase).toBe(3);
88
+ expect(state.phase_history).toHaveLength(3);
89
+ expect(state.phase_history[0].status).toBe('completed');
90
+ expect(state.phase_history[1].status).toBe('completed');
91
+ expect(state.phase_history[2].status).toBe('in_progress');
92
+ });
93
+
94
+ it('updates existing entry when same phase is transitioned again', async () => {
95
+ await handlePhaseTransition(['1', 'in_progress', '--dir', tmpDir]);
96
+ await handlePhaseTransition(['1', 'completed', '--dir', tmpDir]);
97
+
98
+ const stateFile = path.join(tmpDir, '.sprint-state', 'sprint-state.json');
99
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
100
+
101
+ // Should NOT have duplicate entries
102
+ expect(state.phase_history).toHaveLength(1);
103
+ expect(state.phase_history[0].status).toBe('completed');
104
+ expect(state.phase_history[0].completed_at).toBeDefined();
105
+ });
106
+ });
107
+
108
+ describe('renderDashboard()', () => {
109
+ it('returns "No sprint state found" for null state', () => {
110
+ const result = renderDashboard(null);
111
+ expect(result).toBe('No sprint state found');
112
+ });
113
+
114
+ it('renders dashboard with all 6 phases', () => {
115
+ const state = {
116
+ id: 'sprint-test-123',
117
+ task_description: 'Test feature',
118
+ phase: 3,
119
+ status: 'in_progress',
120
+ started_at: '2026-07-21T10:00:00Z',
121
+ phase_history: [
122
+ { phase: 1, phase_name: 'PREP', status: 'completed', duration_seconds: 120 },
123
+ { phase: 2, phase_name: 'DESIGN', status: 'completed', duration_seconds: 300 },
124
+ { phase: 3, phase_name: 'BUILD', status: 'in_progress' },
125
+ ],
126
+ isolation: { branch: 'sprint/test-branch' },
127
+ outputs: {},
128
+ };
129
+
130
+ const result = renderDashboard(state);
131
+
132
+ expect(result).toContain('SPRINT PROGRESS');
133
+ expect(result).toContain('sprint-test-123');
134
+ expect(result).toContain('Test feature');
135
+ expect(result).toContain('sprint/test-branch');
136
+ expect(result).toContain('Phase 1/6');
137
+ expect(result).toContain('Phase 6/6');
138
+ expect(result).toContain('✅'); // completed phases
139
+ expect(result).toContain('🔄'); // in_progress phase
140
+ expect(result).toContain('⏳'); // pending phases
141
+ });
142
+
143
+ it('shows outputs section when outputs exist', () => {
144
+ const state = {
145
+ id: 'sprint-out',
146
+ task_description: 'Feature',
147
+ phase: 2,
148
+ status: 'in_progress',
149
+ started_at: '2026-07-21T10:00:00Z',
150
+ phase_history: [],
151
+ isolation: { branch: 'test' },
152
+ outputs: { spec: 'design.yaml', pr: '#42' },
153
+ };
154
+
155
+ const result = renderDashboard(state);
156
+ expect(result).toContain('输出物');
157
+ expect(result).toContain('spec: design.yaml');
158
+ expect(result).toContain('pr: #42');
159
+ });
160
+
161
+ it('handles minimal state gracefully', () => {
162
+ const state = {
163
+ id: 'sprint-min',
164
+ phase: 1,
165
+ status: 'in_progress',
166
+ phase_history: [],
167
+ };
168
+
169
+ const result = renderDashboard(state);
170
+ expect(result).toContain('SPRINT PROGRESS');
171
+ expect(result).toContain('sprint-min');
172
+ });
173
+ });
174
+ });