@boyingliu01/xp-gate 0.14.9 → 0.14.11
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__/install-skill.test.js +2 -1
- package/lib/__tests__/sprint-discovery.test.ts +6 -4
- package/lib/__tests__/sprint-state-manager.test.js +279 -0
- package/lib/__tests__/sprint-status.test.js +10 -13
- package/lib/__tests__/sprint-status.test.ts +4 -3
- package/lib/install-skill.js +14 -1
- package/lib/next-sprint.js +3 -4
- package/lib/sprint-discovery.js +4 -4
- package/lib/sprint-state-manager.js +122 -0
- package/lib/sprint-state-migrator.js +153 -0
- package/lib/sprint-status.js +4 -4
- package/mock-policy/AGENTS.md +4 -4
- package/mutation/AGENTS.md +4 -4
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
- package/principles/AGENTS.md +4 -4
- package/skills/delphi-review/AGENTS.md +4 -4
- package/skills/sprint-flow/AGENTS.md +4 -4
- package/skills/test-specification-alignment/AGENTS.md +4 -4
|
@@ -178,7 +178,8 @@ describe('install-skill', () => {
|
|
|
178
178
|
expect(fs.existsSync(configFile)).toBe(true);
|
|
179
179
|
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
180
180
|
expect(config.installedSkills).toHaveProperty('delphi-review');
|
|
181
|
-
|
|
181
|
+
// Version should be actual CLI version (0.14.10.0), not hardcoded '1.0.0'
|
|
182
|
+
expect(config.installedSkills['delphi-review'].version).toBe('0.14.10.0');
|
|
182
183
|
expect(config.installedSkills['delphi-review'].installedAt).toMatch(/^\d{4}-/);
|
|
183
184
|
});
|
|
184
185
|
|
|
@@ -322,10 +322,12 @@ describe('sprint-discovery: discoverActiveSprints', () => {
|
|
|
322
322
|
// no id, no status, no started_at
|
|
323
323
|
task_description: 'Partial fields only',
|
|
324
324
|
});
|
|
325
|
-
//
|
|
325
|
+
// After migration, missing id gets auto-generated, so result is found
|
|
326
326
|
const results = sprintDiscovery.discoverActiveSprints(TMP_ROOT);
|
|
327
327
|
const partials = results.filter(r => r.state.task_description === 'Partial fields only');
|
|
328
|
-
expect(partials.length).toBe(
|
|
328
|
+
expect(partials.length).toBe(1);
|
|
329
|
+
// Migrated state should have auto-generated id
|
|
330
|
+
expect(partials[0].state.id).toMatch(/^sprint-\d+$/);
|
|
329
331
|
});
|
|
330
332
|
|
|
331
333
|
test('handles EACCES on .sprint-state/ directory gracefully', () => {
|
|
@@ -359,7 +361,7 @@ describe('sprint-discovery: discoverActiveSprints', () => {
|
|
|
359
361
|
const results = sprintDiscovery.discoverActiveSprints(TMP_ROOT);
|
|
360
362
|
const desc = results.filter(r => r.state.id === 'sprint-2026-06-23-nodesc');
|
|
361
363
|
expect(desc.length).toBe(1);
|
|
362
|
-
//
|
|
363
|
-
expect(desc[0].state.task_description).
|
|
364
|
+
// After migration, missing task_description gets default '-'
|
|
365
|
+
expect(desc[0].state.task_description).toBe('-');
|
|
364
366
|
});
|
|
365
367
|
});
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Sprint State Manager
|
|
3
|
+
* @test REQ-001 Sprint State Manager
|
|
4
|
+
* @intent Verify read/write/migration/transitionPhase/rollback
|
|
5
|
+
* @covers AC-001-01, AC-001-02, AC-001-03
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import { SprintStateManager, LEGACY_PHASE_MAP } from '../sprint-state-manager.js';
|
|
13
|
+
import { migrateState, createInitialState } from '../sprint-state-migrator.js';
|
|
14
|
+
|
|
15
|
+
describe('SprintStateManager', () => {
|
|
16
|
+
let tmpDir;
|
|
17
|
+
let manager;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sprint-state-test-'));
|
|
21
|
+
manager = new SprintStateManager(tmpDir);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('read()', () => {
|
|
29
|
+
it('returns null when file does not exist', () => {
|
|
30
|
+
const state = manager.read();
|
|
31
|
+
expect(state).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('reads valid state file', () => {
|
|
35
|
+
const state = {
|
|
36
|
+
_schema_version: 1,
|
|
37
|
+
id: 'sprint-123',
|
|
38
|
+
task_description: 'Test sprint',
|
|
39
|
+
phase: 2,
|
|
40
|
+
status: 'in_progress',
|
|
41
|
+
started_at: '2026-07-14T10:00:00Z',
|
|
42
|
+
phase_history: [],
|
|
43
|
+
isolation: { worktree_path: tmpDir, branch: 'test' },
|
|
44
|
+
};
|
|
45
|
+
fs.mkdirSync(path.join(tmpDir, '.sprint-state'), { recursive: true });
|
|
46
|
+
fs.writeFileSync(
|
|
47
|
+
path.join(tmpDir, '.sprint-state', 'sprint-state.json'),
|
|
48
|
+
JSON.stringify(state, null, 2)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const result = manager.read();
|
|
52
|
+
expect(result.id).toBe('sprint-123');
|
|
53
|
+
expect(result._schema_version).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('auto-migrates legacy state and creates backup', () => {
|
|
57
|
+
const legacyState = {
|
|
58
|
+
id: 'sprint-legacy',
|
|
59
|
+
task_description: 'Legacy sprint',
|
|
60
|
+
phase: -1, // Legacy ISOLATE → 1 (PREP)
|
|
61
|
+
status: 'completed',
|
|
62
|
+
phase_history: [
|
|
63
|
+
{ phase: -1, phase_name: 'ISOLATE', status: 'completed', timestamp: '2026-07-14T10:00:00Z' },
|
|
64
|
+
],
|
|
65
|
+
isolation: { worktree_path: tmpDir, branch: 'test' },
|
|
66
|
+
};
|
|
67
|
+
fs.mkdirSync(path.join(tmpDir, '.sprint-state'), { recursive: true });
|
|
68
|
+
fs.writeFileSync(
|
|
69
|
+
path.join(tmpDir, '.sprint-state', 'sprint-state.json'),
|
|
70
|
+
JSON.stringify(legacyState, null, 2)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const result = manager.read();
|
|
74
|
+
|
|
75
|
+
// Should be migrated
|
|
76
|
+
expect(result._schema_version).toBe(1);
|
|
77
|
+
expect(result.phase).toBe(1); // -1 → 1
|
|
78
|
+
expect(result.phase_history[0].phase).toBe(1);
|
|
79
|
+
expect(result.phase_history[0].phase_name).toBe('PREP');
|
|
80
|
+
expect(result.phase_history[0].started_at).toBe('2026-07-14T10:00:00Z'); // timestamp → started_at
|
|
81
|
+
|
|
82
|
+
// Backup should exist
|
|
83
|
+
const backupExists = fs.existsSync(path.join(tmpDir, '.sprint-state', 'sprint-state.json.backup'));
|
|
84
|
+
expect(backupExists).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('write()', () => {
|
|
89
|
+
it('writes state atomically', () => {
|
|
90
|
+
const state = {
|
|
91
|
+
_schema_version: 1,
|
|
92
|
+
id: 'sprint-test',
|
|
93
|
+
task_description: 'Test',
|
|
94
|
+
phase: 1,
|
|
95
|
+
status: 'in_progress',
|
|
96
|
+
started_at: new Date().toISOString(),
|
|
97
|
+
phase_history: [],
|
|
98
|
+
isolation: { worktree_path: tmpDir, branch: 'test' },
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
manager.write(state);
|
|
102
|
+
|
|
103
|
+
const written = JSON.parse(
|
|
104
|
+
fs.readFileSync(path.join(tmpDir, '.sprint-state', 'sprint-state.json'), 'utf8')
|
|
105
|
+
);
|
|
106
|
+
expect(written.id).toBe('sprint-test');
|
|
107
|
+
|
|
108
|
+
// Tmp file should not exist (atomic write completed)
|
|
109
|
+
const tmpExists = fs.existsSync(path.join(tmpDir, '.sprint-state', 'sprint-state.json.tmp'));
|
|
110
|
+
expect(tmpExists).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('transitionPhase()', () => {
|
|
115
|
+
it('creates new phase_history entry', () => {
|
|
116
|
+
// Create initial state
|
|
117
|
+
const initialState = createInitialState(tmpDir);
|
|
118
|
+
manager.write(initialState);
|
|
119
|
+
|
|
120
|
+
// Transition to phase 2
|
|
121
|
+
const result = manager.transitionPhase(2, 'in_progress');
|
|
122
|
+
|
|
123
|
+
expect(result.phase).toBe(2);
|
|
124
|
+
expect(result.phase_history.length).toBe(1);
|
|
125
|
+
expect(result.phase_history[0].phase).toBe(2);
|
|
126
|
+
expect(result.phase_history[0].phase_name).toBe('DESIGN');
|
|
127
|
+
expect(result.phase_history[0].status).toBe('in_progress');
|
|
128
|
+
expect(result.phase_history[0].started_at).toBeDefined();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('updates existing phase_history entry', () => {
|
|
132
|
+
const initialState = createInitialState(tmpDir);
|
|
133
|
+
manager.write(initialState);
|
|
134
|
+
|
|
135
|
+
// Start phase 2
|
|
136
|
+
manager.transitionPhase(2, 'in_progress');
|
|
137
|
+
|
|
138
|
+
// Complete phase 2
|
|
139
|
+
const result = manager.transitionPhase(2, 'completed');
|
|
140
|
+
|
|
141
|
+
expect(result.phase_history[0].status).toBe('completed');
|
|
142
|
+
expect(result.phase_history[0].completed_at).toBeDefined();
|
|
143
|
+
expect(result.phase_history[0].duration_seconds).toBeGreaterThanOrEqual(0);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('calls onTransition callback', () => {
|
|
147
|
+
const initialState = createInitialState(tmpDir);
|
|
148
|
+
manager.write(initialState);
|
|
149
|
+
|
|
150
|
+
let callbackCalled = false;
|
|
151
|
+
let callbackState = null;
|
|
152
|
+
|
|
153
|
+
manager.transitionPhase(2, 'in_progress', {
|
|
154
|
+
onTransition: (state) => {
|
|
155
|
+
callbackCalled = true;
|
|
156
|
+
callbackState = state;
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(callbackCalled).toBe(true);
|
|
161
|
+
expect(callbackState.phase).toBe(2);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('handles onTransition callback errors gracefully', () => {
|
|
165
|
+
const initialState = createInitialState(tmpDir);
|
|
166
|
+
manager.write(initialState);
|
|
167
|
+
|
|
168
|
+
// Should not throw
|
|
169
|
+
expect(() => {
|
|
170
|
+
manager.transitionPhase(2, 'in_progress', {
|
|
171
|
+
onTransition: () => {
|
|
172
|
+
throw new Error('Render failed');
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
}).not.toThrow();
|
|
176
|
+
|
|
177
|
+
// State should still be updated
|
|
178
|
+
const state = manager.read();
|
|
179
|
+
expect(state.phase).toBe(2);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('updates outputs when provided', () => {
|
|
183
|
+
const initialState = createInitialState(tmpDir);
|
|
184
|
+
manager.write(initialState);
|
|
185
|
+
|
|
186
|
+
const result = manager.transitionPhase(2, 'completed', {
|
|
187
|
+
outputs: { specification: 'spec.yaml' },
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(result.outputs.specification).toBe('spec.yaml');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('migrate()', () => {
|
|
195
|
+
it('adds _schema_version', () => {
|
|
196
|
+
const state = { id: 'test', phase: 1, phase_history: [] };
|
|
197
|
+
const migrated = migrateState(state, manager.stateDir);
|
|
198
|
+
expect(migrated._schema_version).toBe(1);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('sets default task_description if missing', () => {
|
|
202
|
+
const state = { id: 'test', phase: 1, phase_history: [] };
|
|
203
|
+
const migrated = migrateState(state, manager.stateDir);
|
|
204
|
+
expect(migrated.task_description).toBe('-');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('migrates all legacy phase numbers', () => {
|
|
208
|
+
for (const [legacy, expected] of Object.entries(LEGACY_PHASE_MAP)) {
|
|
209
|
+
const state = {
|
|
210
|
+
id: 'test',
|
|
211
|
+
phase: Number(legacy),
|
|
212
|
+
phase_history: [{ phase: Number(legacy), status: 'completed' }],
|
|
213
|
+
};
|
|
214
|
+
const migrated = migrateState(state, manager.stateDir);
|
|
215
|
+
expect(migrated.phase).toBe(expected.phase);
|
|
216
|
+
expect(migrated.phase_history[0].phase).toBe(expected.phase);
|
|
217
|
+
expect(migrated.phase_history[0].phase_name).toBe(expected.phase_name);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('preserves unknown fields', () => {
|
|
222
|
+
const state = {
|
|
223
|
+
id: 'test',
|
|
224
|
+
phase: 1,
|
|
225
|
+
phase_history: [],
|
|
226
|
+
custom_field: 'preserved',
|
|
227
|
+
};
|
|
228
|
+
const migrated = migrateState(state, manager.stateDir);
|
|
229
|
+
expect(migrated.custom_field).toBe('preserved');
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('rollback()', () => {
|
|
234
|
+
it('returns false when no backup exists', () => {
|
|
235
|
+
const result = manager.rollback();
|
|
236
|
+
expect(result).toBe(false);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('restores from backup', () => {
|
|
240
|
+
// Create initial state and backup
|
|
241
|
+
const originalState = {
|
|
242
|
+
_schema_version: 1,
|
|
243
|
+
id: 'original',
|
|
244
|
+
task_description: 'Original',
|
|
245
|
+
phase: 1,
|
|
246
|
+
status: 'in_progress',
|
|
247
|
+
started_at: '2026-07-14T10:00:00Z',
|
|
248
|
+
phase_history: [],
|
|
249
|
+
isolation: { worktree_path: tmpDir, branch: 'test' },
|
|
250
|
+
};
|
|
251
|
+
fs.mkdirSync(path.join(tmpDir, '.sprint-state'), { recursive: true });
|
|
252
|
+
fs.writeFileSync(
|
|
253
|
+
path.join(tmpDir, '.sprint-state', 'sprint-state.json'),
|
|
254
|
+
JSON.stringify(originalState, null, 2)
|
|
255
|
+
);
|
|
256
|
+
fs.writeFileSync(
|
|
257
|
+
path.join(tmpDir, '.sprint-state', 'sprint-state.json.backup'),
|
|
258
|
+
JSON.stringify(originalState, null, 2)
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// Modify current state
|
|
262
|
+
const modifiedState = { ...originalState, id: 'modified' };
|
|
263
|
+
fs.writeFileSync(
|
|
264
|
+
path.join(tmpDir, '.sprint-state', 'sprint-state.json'),
|
|
265
|
+
JSON.stringify(modifiedState, null, 2)
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Rollback
|
|
269
|
+
const result = manager.rollback();
|
|
270
|
+
expect(result).toBe(true);
|
|
271
|
+
|
|
272
|
+
// Verify restored
|
|
273
|
+
const restored = JSON.parse(
|
|
274
|
+
fs.readFileSync(path.join(tmpDir, '.sprint-state', 'sprint-state.json'), 'utf8')
|
|
275
|
+
);
|
|
276
|
+
expect(restored.id).toBe('original');
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
});
|
|
@@ -14,12 +14,13 @@ const TMP_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'sprint-status-test-'));
|
|
|
14
14
|
const STATE_DIR = path.join(TMP_DIR, '.sprint-state');
|
|
15
15
|
const STATE_FILE = path.join(STATE_DIR, 'sprint-state.json');
|
|
16
16
|
|
|
17
|
-
// Active sprint state (
|
|
17
|
+
// Active sprint state (v1 schema per sprint-state-manager.js)
|
|
18
18
|
const ACTIVE_STATE = {
|
|
19
|
+
_schema_version: 1,
|
|
19
20
|
id: 'sprint-2026-06-16-01',
|
|
20
21
|
task_description: 'Test sprint',
|
|
21
|
-
phase:
|
|
22
|
-
status: '
|
|
22
|
+
phase: 3,
|
|
23
|
+
status: 'in_progress',
|
|
23
24
|
started_at: '2026-06-16T10:00:00Z',
|
|
24
25
|
isolation: {
|
|
25
26
|
worktree_path: '.worktrees/sprint/sprint-2026-06-16-01',
|
|
@@ -33,15 +34,11 @@ const ACTIVE_STATE = {
|
|
|
33
34
|
user_decision: 'accepted'
|
|
34
35
|
},
|
|
35
36
|
phase_history: [
|
|
36
|
-
{ phase:
|
|
37
|
+
{ phase: 1, phase_name: 'PREP', status: 'completed',
|
|
37
38
|
started_at: '2026-06-16T10:00:00Z', completed_at: '2026-06-16T10:03:00Z', duration_seconds: 180 },
|
|
38
|
-
{ phase:
|
|
39
|
-
started_at: '2026-06-16T10:03:00Z', completed_at: '2026-06-16T10:05:00Z', duration_seconds: 120 },
|
|
40
|
-
{ phase: 0, phase_name: 'THINK', status: 'completed',
|
|
39
|
+
{ phase: 2, phase_name: 'DESIGN', status: 'completed',
|
|
41
40
|
started_at: '2026-06-16T10:05:00Z', completed_at: '2026-06-16T10:15:00Z', duration_seconds: 600 },
|
|
42
|
-
{ phase:
|
|
43
|
-
started_at: '2026-06-16T10:15:00Z', completed_at: '2026-06-16T10:25:00Z', duration_seconds: 600 },
|
|
44
|
-
{ phase: 2, phase_name: 'BUILD', status: 'in_progress',
|
|
41
|
+
{ phase: 3, phase_name: 'BUILD', status: 'in_progress',
|
|
45
42
|
started_at: '2026-06-16T10:25:00Z', completed_at: null, duration_seconds: null,
|
|
46
43
|
reqs: {
|
|
47
44
|
'REQ-001': { name: 'Feature A', status: 'completed' },
|
|
@@ -87,8 +84,8 @@ describe('sprint-status: readSprintState', () => {
|
|
|
87
84
|
const state = sprintStatus.readSprintState(TMP_DIR);
|
|
88
85
|
expect(state).not.toBeNull();
|
|
89
86
|
expect(state.task_description).toBe('Test sprint');
|
|
90
|
-
expect(state.phase).toBe(
|
|
91
|
-
expect(state.status).toBe('
|
|
87
|
+
expect(state.phase).toBe(3);
|
|
88
|
+
expect(state.status).toBe('in_progress');
|
|
92
89
|
expect(Array.isArray(state.phase_history)).toBe(true);
|
|
93
90
|
expect(state.phase_history.length).toBeGreaterThan(0);
|
|
94
91
|
});
|
|
@@ -166,7 +163,7 @@ describe('sprint-status: formatSprintTable', () => {
|
|
|
166
163
|
const json = sprintStatus.jsonMode(ACTIVE_STATE);
|
|
167
164
|
const parsed = JSON.parse(json);
|
|
168
165
|
expect(parsed.task_description).toBe('Test sprint');
|
|
169
|
-
expect(parsed.phase).toBe(
|
|
166
|
+
expect(parsed.phase).toBe(3);
|
|
170
167
|
});
|
|
171
168
|
|
|
172
169
|
// AC-SPRINTSTATUS-001-09: Stale state detection
|
|
@@ -16,12 +16,13 @@ const TMP_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'sprint-status-test-'));
|
|
|
16
16
|
const STATE_DIR = path.join(TMP_DIR, '.sprint-state');
|
|
17
17
|
const STATE_FILE = path.join(STATE_DIR, 'sprint-state.json');
|
|
18
18
|
|
|
19
|
-
// Active sprint state (
|
|
19
|
+
// Active sprint state (v1 schema per sprint-state-manager.js, 6-phase schema)
|
|
20
20
|
const ACTIVE_STATE = {
|
|
21
|
+
_schema_version: 1,
|
|
21
22
|
id: 'sprint-2026-06-16-01',
|
|
22
23
|
task_description: 'Test sprint',
|
|
23
24
|
phase: 3,
|
|
24
|
-
status: '
|
|
25
|
+
status: 'in_progress',
|
|
25
26
|
started_at: '2026-06-16T10:00:00Z',
|
|
26
27
|
isolation: {
|
|
27
28
|
worktree_path: '.worktrees/sprint/sprint-2026-06-16-01',
|
|
@@ -95,7 +96,7 @@ describe('sprint-status: readSprintState', () => {
|
|
|
95
96
|
const stateObj = state as Record<string, unknown>;
|
|
96
97
|
expect(stateObj.task_description).toBe('Test sprint');
|
|
97
98
|
expect(stateObj.phase).toBe(3);
|
|
98
|
-
expect(stateObj.status).toBe('
|
|
99
|
+
expect(stateObj.status).toBe('in_progress');
|
|
99
100
|
expect(Array.isArray(stateObj.phase_history)).toBe(true);
|
|
100
101
|
});
|
|
101
102
|
|
package/lib/install-skill.js
CHANGED
|
@@ -20,6 +20,15 @@ function getSkillsDir() {
|
|
|
20
20
|
return path.join(HOME_DIR, '.config', 'opencode', 'skills');
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function getCliVersion() {
|
|
24
|
+
try {
|
|
25
|
+
const versionFile = path.join(__dirname, '..', '..', '..', 'VERSION');
|
|
26
|
+
return fs.readFileSync(versionFile, 'utf8').trim();
|
|
27
|
+
} catch {
|
|
28
|
+
return '0.0.0';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
const SKILLS_REGISTRY = {
|
|
24
33
|
'sprint-flow': { repo: 'boyingliu01/xp-gate', path: 'skills/sprint-flow' },
|
|
25
34
|
'delphi-review': { repo: 'boyingliu01/xp-gate', path: 'skills/delphi-review' },
|
|
@@ -123,10 +132,14 @@ async function performInstall(skillInfo, name, targetDir, offline, verbose) {
|
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
ensureConfigDir();
|
|
135
|
+
|
|
136
|
+
// Read actual CLI version from VERSION file
|
|
137
|
+
const version = getCliVersion();
|
|
138
|
+
|
|
126
139
|
updateConfig({
|
|
127
140
|
installedSkills: {
|
|
128
141
|
...(getConfig().installedSkills || {}),
|
|
129
|
-
[name]: { version
|
|
142
|
+
[name]: { version, installedAt: new Date().toISOString() }
|
|
130
143
|
}
|
|
131
144
|
});
|
|
132
145
|
|
package/lib/next-sprint.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
const childProcess = require('child_process');
|
|
2
|
-
const fs = require('fs');
|
|
3
2
|
const path = require('path');
|
|
3
|
+
const { SprintStateManager } = require('./sprint-state-manager');
|
|
4
4
|
|
|
5
5
|
function readSprintState(dir) {
|
|
6
6
|
try {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
return JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
7
|
+
const manager = new SprintStateManager(dir);
|
|
8
|
+
return manager.read();
|
|
10
9
|
} catch {
|
|
11
10
|
return null;
|
|
12
11
|
}
|
package/lib/sprint-discovery.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
+
const { SprintStateManager } = require('./sprint-state-manager');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Maximum number of active sprints to return.
|
|
@@ -62,16 +63,15 @@ function findGitRoot(startDir) {
|
|
|
62
63
|
|
|
63
64
|
/**
|
|
64
65
|
* Read and parse a sprint-state.json from a given project directory.
|
|
66
|
+
* Uses SprintStateManager for schema validation + auto-migration.
|
|
65
67
|
*
|
|
66
68
|
* @param {string} dir - Directory containing .sprint-state/
|
|
67
69
|
* @returns {object|null} Parsed sprint state, or null if not found or malformed
|
|
68
70
|
*/
|
|
69
71
|
function readSprintState(dir) {
|
|
70
72
|
try {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
const raw = fs.readFileSync(stateFile, 'utf8');
|
|
74
|
-
return JSON.parse(raw);
|
|
73
|
+
const manager = new SprintStateManager(dir);
|
|
74
|
+
return manager.read();
|
|
75
75
|
} catch {
|
|
76
76
|
return null;
|
|
77
77
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sprint State Manager — centralized read/write/transition for sprint state.
|
|
3
|
+
*
|
|
4
|
+
* Resolves #343 (schema drift) and #338 (auto-render enforcement).
|
|
5
|
+
*
|
|
6
|
+
* @module sprint-state-manager
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { migrateState, createInitialState, rollbackState, LEGACY_PHASE_MAP } = require('./sprint-state-migrator');
|
|
14
|
+
|
|
15
|
+
class SprintStateManager {
|
|
16
|
+
constructor(projectRoot) {
|
|
17
|
+
this.projectRoot = projectRoot;
|
|
18
|
+
this.stateDir = path.join(projectRoot, '.sprint-state');
|
|
19
|
+
this.stateFile = path.join(this.stateDir, 'sprint-state.json');
|
|
20
|
+
this.backupFile = path.join(this.stateDir, 'sprint-state.json.backup');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
read() {
|
|
24
|
+
if (!fs.existsSync(this.stateFile)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const raw = fs.readFileSync(this.stateFile, 'utf8');
|
|
29
|
+
let state;
|
|
30
|
+
try {
|
|
31
|
+
state = JSON.parse(raw);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
throw new Error(`Failed to parse sprint-state.json: ${err.message}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check if migration needed
|
|
37
|
+
if (!state._schema_version) {
|
|
38
|
+
// Create backup before first migration
|
|
39
|
+
if (!fs.existsSync(this.backupFile)) {
|
|
40
|
+
fs.copyFileSync(this.stateFile, this.backupFile);
|
|
41
|
+
}
|
|
42
|
+
state = migrateState(state, this.stateDir);
|
|
43
|
+
// Write migrated state atomically
|
|
44
|
+
this.write(state);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return state;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
write(state) {
|
|
51
|
+
// Ensure state directory exists
|
|
52
|
+
if (!fs.existsSync(this.stateDir)) {
|
|
53
|
+
fs.mkdirSync(this.stateDir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Atomic write: write to tmp, then rename
|
|
57
|
+
const tmpFile = this.stateFile + '.tmp';
|
|
58
|
+
fs.writeFileSync(tmpFile, JSON.stringify(state, null, 2), 'utf8');
|
|
59
|
+
fs.renameSync(tmpFile, this.stateFile);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
transitionPhase(phase, status, options = {}) {
|
|
63
|
+
const state = this.read() || createInitialState(this.projectRoot);
|
|
64
|
+
|
|
65
|
+
// Update current phase
|
|
66
|
+
state.phase = phase;
|
|
67
|
+
|
|
68
|
+
// Find or create phase_history entry
|
|
69
|
+
const phaseNames = ['PREP', 'DESIGN', 'BUILD', 'VERIFY', 'SHIP', 'CLOSE'];
|
|
70
|
+
const phaseName = phaseNames[phase - 1];
|
|
71
|
+
|
|
72
|
+
let entry = state.phase_history.find(e => e.phase === phase);
|
|
73
|
+
if (!entry) {
|
|
74
|
+
entry = {
|
|
75
|
+
phase,
|
|
76
|
+
phase_name: phaseName,
|
|
77
|
+
status,
|
|
78
|
+
};
|
|
79
|
+
state.phase_history.push(entry);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Update entry
|
|
83
|
+
entry.status = status;
|
|
84
|
+
if (status === 'in_progress' && !entry.started_at) {
|
|
85
|
+
entry.started_at = new Date().toISOString();
|
|
86
|
+
}
|
|
87
|
+
if (status === 'completed' || status === 'skipped') {
|
|
88
|
+
entry.completed_at = new Date().toISOString();
|
|
89
|
+
if (entry.started_at) {
|
|
90
|
+
const startMs = new Date(entry.started_at).getTime();
|
|
91
|
+
const endMs = new Date(entry.completed_at).getTime();
|
|
92
|
+
entry.duration_seconds = Math.round((endMs - startMs) / 1000);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Update outputs if provided
|
|
97
|
+
if (options.outputs) {
|
|
98
|
+
state.outputs = { ...state.outputs, ...options.outputs };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Write state atomically
|
|
102
|
+
this.write(state);
|
|
103
|
+
|
|
104
|
+
// Trigger onTransition callback (for auto-render)
|
|
105
|
+
if (typeof options.onTransition === 'function') {
|
|
106
|
+
try {
|
|
107
|
+
options.onTransition(state);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
// Render failure is WARNING, not BLOCK
|
|
110
|
+
console.warn(`[WARN] onTransition callback failed: ${err.message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return state;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
rollback() {
|
|
118
|
+
return rollbackState(this.stateFile, this.backupFile);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { SprintStateManager, LEGACY_PHASE_MAP };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sprint State Migrator — legacy phase migration + rollback helpers.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from SprintStateManager to satisfy god-class rule (≤15 methods).
|
|
5
|
+
*
|
|
6
|
+
* @module sprint-state-migrator
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Legacy phase number → v1 phase mapping
|
|
16
|
+
*/
|
|
17
|
+
const LEGACY_PHASE_MAP = {
|
|
18
|
+
'-1': { phase: 1, phase_name: 'PREP' },
|
|
19
|
+
'-0.5': { phase: 1, phase_name: 'PREP' },
|
|
20
|
+
'0': { phase: 2, phase_name: 'DESIGN' },
|
|
21
|
+
'1': { phase: 2, phase_name: 'DESIGN' },
|
|
22
|
+
'2': { phase: 3, phase_name: 'BUILD' },
|
|
23
|
+
'3': { phase: 4, phase_name: 'VERIFY' },
|
|
24
|
+
'4': { phase: 4, phase_name: 'VERIFY' },
|
|
25
|
+
'5': { phase: 5, phase_name: 'SHIP' },
|
|
26
|
+
'6': { phase: 5, phase_name: 'SHIP' },
|
|
27
|
+
'7': { phase: 6, phase_name: 'CLOSE' },
|
|
28
|
+
'8': { phase: 6, phase_name: 'CLOSE' },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const PHASE_NAMES = ['PREP', 'DESIGN', 'BUILD', 'VERIFY', 'SHIP', 'CLOSE'];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Migrate legacy sprint-state.json to v1 schema.
|
|
35
|
+
*
|
|
36
|
+
* @param {Object} state - Raw state from file
|
|
37
|
+
* @param {string} stateDir - Path to .sprint-state directory
|
|
38
|
+
* @returns {Object} Migrated state with _schema_version: 1
|
|
39
|
+
*/
|
|
40
|
+
function migrateState(state, stateDir) {
|
|
41
|
+
const warnings = [];
|
|
42
|
+
|
|
43
|
+
state._schema_version = 1;
|
|
44
|
+
|
|
45
|
+
if (!state.task_description) {
|
|
46
|
+
state.task_description = '-';
|
|
47
|
+
warnings.push('task_description was missing, set to "-"');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!state.id) {
|
|
51
|
+
state.id = `sprint-${Date.now()}`;
|
|
52
|
+
warnings.push('id was missing, generated from timestamp');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!state.status) { state.status = 'in_progress'; }
|
|
56
|
+
if (!state.started_at) { state.started_at = new Date().toISOString(); }
|
|
57
|
+
if (!state.phase_history) { state.phase_history = []; }
|
|
58
|
+
if (!state.isolation) { state.isolation = { worktree_path: null, branch: null }; }
|
|
59
|
+
|
|
60
|
+
state.phase_history = state.phase_history.map(entry => {
|
|
61
|
+
const phaseNum = String(entry.phase);
|
|
62
|
+
const mapped = LEGACY_PHASE_MAP[phaseNum];
|
|
63
|
+
if (mapped) {
|
|
64
|
+
warnings.push(`Migrated legacy phase ${entry.phase} → ${mapped.phase} (${mapped.phase_name})`);
|
|
65
|
+
return { ...entry, phase: mapped.phase, phase_name: mapped.phase_name };
|
|
66
|
+
}
|
|
67
|
+
if (!entry.phase_name && entry.phase >= 1 && entry.phase <= 6) {
|
|
68
|
+
entry.phase_name = PHASE_NAMES[entry.phase - 1];
|
|
69
|
+
}
|
|
70
|
+
return entry;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const currentPhaseNum = String(state.phase);
|
|
74
|
+
const currentMapped = LEGACY_PHASE_MAP[currentPhaseNum];
|
|
75
|
+
if (currentMapped) {
|
|
76
|
+
state.phase = currentMapped.phase;
|
|
77
|
+
warnings.push(`Migrated current phase ${currentPhaseNum} → ${currentMapped.phase}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
state.phase_history = state.phase_history.map(entry => {
|
|
81
|
+
if (entry.timestamp && !entry.started_at) {
|
|
82
|
+
entry.started_at = entry.timestamp;
|
|
83
|
+
}
|
|
84
|
+
return entry;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (warnings.length > 0) {
|
|
88
|
+
logMigrationWarnings(warnings, stateDir);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return state;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create initial sprint state for new sprints.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} projectRoot - Project root directory
|
|
98
|
+
* @returns {Object} Initial state
|
|
99
|
+
*/
|
|
100
|
+
function createInitialState(projectRoot) {
|
|
101
|
+
return {
|
|
102
|
+
_schema_version: 1,
|
|
103
|
+
id: `sprint-${Date.now()}`,
|
|
104
|
+
task_description: '-',
|
|
105
|
+
phase: 1,
|
|
106
|
+
status: 'in_progress',
|
|
107
|
+
started_at: new Date().toISOString(),
|
|
108
|
+
phase_history: [],
|
|
109
|
+
isolation: { worktree_path: projectRoot, branch: null },
|
|
110
|
+
outputs: {},
|
|
111
|
+
metrics: {},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Log migration warnings to file.
|
|
117
|
+
*
|
|
118
|
+
* @param {string[]} warnings - Array of warning messages
|
|
119
|
+
* @param {string} stateDir - Path to .sprint-state directory
|
|
120
|
+
*/
|
|
121
|
+
function logMigrationWarnings(warnings, stateDir) {
|
|
122
|
+
if (!fs.existsSync(stateDir)) {
|
|
123
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const warningsFile = path.join(stateDir, 'migration-warnings.json');
|
|
127
|
+
const existing = fs.existsSync(warningsFile)
|
|
128
|
+
? JSON.parse(fs.readFileSync(warningsFile, 'utf8'))
|
|
129
|
+
: [];
|
|
130
|
+
|
|
131
|
+
existing.push({ timestamp: new Date().toISOString(), warnings });
|
|
132
|
+
fs.writeFileSync(warningsFile, JSON.stringify(existing, null, 2), 'utf8');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Rollback to backup state.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} stateFile - Path to sprint-state.json
|
|
139
|
+
* @param {string} backupFile - Path to sprint-state.json.backup
|
|
140
|
+
* @returns {boolean} True if rollback succeeded
|
|
141
|
+
*/
|
|
142
|
+
function rollbackState(stateFile, backupFile) {
|
|
143
|
+
if (!fs.existsSync(backupFile)) { return false; }
|
|
144
|
+
fs.copyFileSync(backupFile, stateFile);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = {
|
|
149
|
+
LEGACY_PHASE_MAP,
|
|
150
|
+
migrateState,
|
|
151
|
+
createInitialState,
|
|
152
|
+
rollbackState,
|
|
153
|
+
};
|
package/lib/sprint-status.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const { discoverActiveSprints } = require('./sprint-discovery');
|
|
13
|
+
const { SprintStateManager } = require('./sprint-state-manager');
|
|
13
14
|
|
|
14
15
|
// Phase constants (inlined; was shared-phase-constants.js, removed in v0.13.0 slimming)
|
|
15
16
|
const PHASE_NAMES = {
|
|
@@ -47,15 +48,14 @@ function isStale(state) {
|
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* Read and parse sprint-state.json from a project directory.
|
|
51
|
+
* Uses SprintStateManager for schema validation + auto-migration.
|
|
50
52
|
* @param {string} dir - Project root directory
|
|
51
53
|
* @returns {object|null} Parsed sprint state, or null if not found or malformed
|
|
52
54
|
*/
|
|
53
55
|
function readSprintState(dir) {
|
|
54
56
|
try {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
const raw = fs.readFileSync(stateFile, 'utf8');
|
|
58
|
-
return JSON.parse(raw);
|
|
57
|
+
const manager = new SprintStateManager(dir);
|
|
58
|
+
return manager.read();
|
|
59
59
|
} catch {
|
|
60
60
|
return null;
|
|
61
61
|
}
|
package/mock-policy/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MOCK-POLICY KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Mock layering policy enforcement — Gate M3 of pre-push hook. Ensures integration tests use real implementations for internal dependencies, mock external dependencies, and annotate pending mocks with removal plans. Combines project scope scanning, mock decision engine, and per-file validation into a single pipeline.
|
package/mutation/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MUTATION KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.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.14.
|
|
3
|
+
"version": "0.14.11",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 10 quality gates (Gate 0-9), Sprint Flow (11 phases), and Delphi multi-expert review (>=90% consensus).",
|
|
6
6
|
"author": {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xp-gate",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.11",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Qoder. Includes 10 quality gates (Gate 0-9), Sprint Flow (11 phases), and Delphi multi-expert review (>=90% consensus).",
|
|
6
6
|
"author": {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
package/principles/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# PRINCIPLES CHECKER MODULE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Clean Code & SOLID principles checker — **Gate 4** of pre-commit. 14 rules × 9 language adapters, SARIF 2.1.0 output. Houses the **Boy Scout Rule** enforcement engine (Gate 6) and warning-baseline storage.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.14.
|
|
3
|
+
**Generated:** 2026-07-16
|
|
4
|
+
**Commit:** 2f70dd5
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.14.11.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|