@boyingliu01/xp-gate 0.12.9 → 0.12.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapter-common.sh +13 -18
- package/adapters/typescript.sh +87 -10
- package/gate-3.sh +1 -1
- package/hooks/adapter-common.sh +13 -18
- package/hooks/pre-commit +166 -27
- package/hooks/pre-push +231 -18
- package/lib/__tests__/doctor.test.js +65 -38
- package/lib/__tests__/sprint-discovery.test.ts +5 -4
- package/lib/__tests__/sprint-status.test.ts +5 -4
- package/lib/__tests__/tsconfig.json +8 -0
- package/lib/check-version.js +19 -4
- package/lib/init.js +24 -0
- package/lib/upgrade.js +64 -0
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/mutation/runners/stryker-runner.ts +7 -5
- 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 +117 -14
- package/plugins/claude-code/skills/sprint-flow/__tests__/sprint-flow.test.cjs +444 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-2-build.md +104 -0
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- 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 +117 -14
- package/plugins/opencode/skills/sprint-flow/__tests__/sprint-flow.test.cjs +444 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-2-build.md +104 -0
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/principles/__tests__/baseline-storage.test.ts +3 -2
- package/principles/__tests__/baseline.test.ts +16 -14
- package/principles/__tests__/config.test.ts +1 -1
- package/principles/__tests__/types.test.ts +1 -0
- package/principles/adapters/__tests__/base.test.ts +8 -8
- package/principles/adapters/__tests__/cpp.test.ts +26 -26
- package/principles/adapters/__tests__/dart.test.ts +11 -11
- package/principles/adapters/__tests__/go.test.ts +9 -9
- package/principles/adapters/__tests__/java.test.ts +9 -9
- package/principles/adapters/__tests__/kotlin.test.ts +10 -10
- package/principles/adapters/__tests__/objectivec.test.ts +27 -27
- package/principles/adapters/__tests__/python.test.ts +11 -11
- package/principles/adapters/__tests__/swift.test.ts +9 -9
- package/principles/adapters/__tests__/typescript.test.ts +13 -13
- package/principles/config.ts +1 -1
- package/principles/rules/__tests__/clean-code/large-file.test.ts +10 -9
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/sprint-flow/SKILL.md +117 -14
- package/skills/sprint-flow/__tests__/sprint-flow.test.cjs +444 -0
- package/skills/sprint-flow/references/phase-2-build.md +104 -0
- package/skills/test-specification-alignment/AGENTS.md +3 -3
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-SF-001 Sprint Flow Trigger Accuracy
|
|
3
|
+
* @intent Verify that sprint-flow SKILL.md trigger detection correctly distinguishes
|
|
4
|
+
* between valid sprint-flow requests and unrelated queries
|
|
5
|
+
* @covers AC-SF-001-01, AC-SF-001-02, AC-SF-001-03
|
|
6
|
+
*/
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const SKILL_MD_PATH = path.join(__dirname, '..', 'SKILL.md');
|
|
11
|
+
const REFERENCES_DIR = path.join(__dirname, '..', 'references');
|
|
12
|
+
const PHASE_2_BUILD_PATH = path.join(REFERENCES_DIR, 'phase-2-build.md');
|
|
13
|
+
const ORCHESTRATION_RULES_PATH = path.join(REFERENCES_DIR, 'orchestration-rules.md');
|
|
14
|
+
|
|
15
|
+
let skillContent = '';
|
|
16
|
+
|
|
17
|
+
function loadSkillMd() {
|
|
18
|
+
skillContent = fs.readFileSync(SKILL_MD_PATH, 'utf-8');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseFrontmatter(content) {
|
|
22
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
23
|
+
if (!match) return {};
|
|
24
|
+
const yaml = match[1];
|
|
25
|
+
const result = {};
|
|
26
|
+
// Parse simple YAML key-value pairs and lists
|
|
27
|
+
const lines = yaml.split('\n');
|
|
28
|
+
let currentKey = null;
|
|
29
|
+
for (const line of lines) {
|
|
30
|
+
const trimmed = line.trim();
|
|
31
|
+
if (trimmed === '' || trimmed.startsWith('#')) continue;
|
|
32
|
+
const kvMatch = trimmed.match(/^(\w[\w_]*):\s*(.*)/);
|
|
33
|
+
if (kvMatch) {
|
|
34
|
+
currentKey = kvMatch[1];
|
|
35
|
+
const val = kvMatch[2].trim();
|
|
36
|
+
if (val === '') {
|
|
37
|
+
result[currentKey] = [];
|
|
38
|
+
} else if (val.startsWith('"') || val.startsWith("'")) {
|
|
39
|
+
result[currentKey] = val.slice(1, -1);
|
|
40
|
+
} else {
|
|
41
|
+
result[currentKey] = val;
|
|
42
|
+
}
|
|
43
|
+
} else if (trimmed.startsWith('- ') && currentKey) {
|
|
44
|
+
const item = trimmed.slice(2).trim().replace(/^["'](.*)["'].*$/, '$1');
|
|
45
|
+
if (!Array.isArray(result[currentKey])) result[currentKey] = [];
|
|
46
|
+
result[currentKey].push(item);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// === Trigger Accuracy Tests ===
|
|
53
|
+
|
|
54
|
+
function testPositiveTriggersArePresent() {
|
|
55
|
+
const frontmatter = parseFrontmatter(skillContent);
|
|
56
|
+
const triggers = frontmatter.triggers || [];
|
|
57
|
+
const required = ['/sprint-flow', 'start sprint', '开发新功能', '一键开发'];
|
|
58
|
+
const missing = required.filter(r => !triggers.some(t => t.includes(r)));
|
|
59
|
+
if (missing.length > 0) {
|
|
60
|
+
throw new Error(`Missing positive triggers: ${missing.join(', ')}`);
|
|
61
|
+
}
|
|
62
|
+
console.log(' ✓ Positive triggers are present');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function testNegativeTriggersArePresent() {
|
|
66
|
+
const frontmatter = parseFrontmatter(skillContent);
|
|
67
|
+
const negTriggers = frontmatter.triggers_negative_examples || [];
|
|
68
|
+
if (negTriggers.length < 8) {
|
|
69
|
+
throw new Error(`triggers_negative_examples must have at least 8 entries, got ${negTriggers.length}`);
|
|
70
|
+
}
|
|
71
|
+
console.log(` ✓ Negative triggers have ${negTriggers.length} entries (>= 8 required)`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function testNegativeTriggerPhrases() {
|
|
75
|
+
// These phrases must NOT trigger sprint-flow
|
|
76
|
+
const shouldNotTrigger = [
|
|
77
|
+
'实现排序算法',
|
|
78
|
+
'实现一下',
|
|
79
|
+
'帮我实现这个函数',
|
|
80
|
+
'怎么实现登录功能',
|
|
81
|
+
'start spring boot',
|
|
82
|
+
'开发环境配置',
|
|
83
|
+
'一键部署',
|
|
84
|
+
'新功能建议',
|
|
85
|
+
'implement a sort function',
|
|
86
|
+
'how to implement auth',
|
|
87
|
+
];
|
|
88
|
+
for (const phrase of shouldNotTrigger) {
|
|
89
|
+
// Verify phrase is in triggers_negative_examples
|
|
90
|
+
const frontmatter = parseFrontmatter(skillContent);
|
|
91
|
+
const negTriggers = frontmatter.triggers_negative_examples || [];
|
|
92
|
+
const found = negTriggers.some(n => n.includes(phrase) || phrase.includes(n));
|
|
93
|
+
if (!found) {
|
|
94
|
+
throw new Error(`Negative trigger phrase "${phrase}" not found in triggers_negative_examples`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
console.log(` ✓ All ${shouldNotTrigger.length} negative trigger phrases verified`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function testPositiveTriggerPhrases() {
|
|
101
|
+
const shouldTrigger = [
|
|
102
|
+
'开发用户认证模块',
|
|
103
|
+
'/sprint-flow',
|
|
104
|
+
'一键开发 REST API',
|
|
105
|
+
];
|
|
106
|
+
for (const phrase of shouldTrigger) {
|
|
107
|
+
const frontmatter = parseFrontmatter(skillContent);
|
|
108
|
+
const triggers = frontmatter.triggers || [];
|
|
109
|
+
// Match against frontmatter triggers OR check body trigger table for broader patterns
|
|
110
|
+
const matched = triggers.some(t =>
|
|
111
|
+
phrase.includes(t) || t.includes(phrase) || phrase.includes('/sprint-flow')
|
|
112
|
+
) || skillContent.includes(phrase);
|
|
113
|
+
if (!matched) {
|
|
114
|
+
throw new Error(`Positive trigger phrase "${phrase}" should match at least one trigger`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
console.log(` ✓ All ${shouldTrigger.length} positive trigger phrases verified`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function testNegativeTestCasesExist() {
|
|
121
|
+
// Count test case entries by matching "input:" lines in triggers_negative_test_cases YAML block
|
|
122
|
+
const fmMatch = skillContent.match(/^---\n([\s\S]*?)\n---/);
|
|
123
|
+
if (!fmMatch) throw new Error('Frontmatter not found');
|
|
124
|
+
const yaml = fmMatch[1];
|
|
125
|
+
|
|
126
|
+
// Find triggers_negative_test_cases block and count "- input:" lines within it
|
|
127
|
+
const testCasesSection = yaml.split('triggers_negative_test_cases:')[1];
|
|
128
|
+
if (!testCasesSection) throw new Error('triggers_negative_test_cases not found in frontmatter');
|
|
129
|
+
|
|
130
|
+
const count = (testCasesSection.match(/^\s+- input:/gm) || []).length;
|
|
131
|
+
if (count < 11) {
|
|
132
|
+
throw new Error(`triggers_negative_test_cases must have at least 11 entries, got ${count}`);
|
|
133
|
+
}
|
|
134
|
+
console.log(` ✓ Negative test cases have ${count} entries (>= 11 required)`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// === Phase Transition Tests ===
|
|
138
|
+
|
|
139
|
+
function testWorkflowStepsOrder() {
|
|
140
|
+
const frontmatter = parseFrontmatter(skillContent);
|
|
141
|
+
const steps = frontmatter.workflow_steps || [];
|
|
142
|
+
if (steps.length !== 11) {
|
|
143
|
+
throw new Error(`workflow_steps must have exactly 11 entries, got ${steps.length}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const expectedOrder = [
|
|
147
|
+
'ISOLATE', 'AUTO-ESTIMATE', 'THINK', 'PLAN', 'BUILD',
|
|
148
|
+
'REVIEW', 'FEEDBACK', 'SHIP', 'LAND', 'USER ACCEPTANCE', 'CLEANUP',
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
for (let i = 0; i < expectedOrder.length; i++) {
|
|
152
|
+
if (!steps[i].includes(expectedOrder[i])) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`workflow_steps[${i}] should contain "${expectedOrder[i]}", got "${steps[i]}"`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
console.log(' ✓ workflow_steps order matches canonical 11-phase sequence');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function testPhaseFlowDiagramOrder() {
|
|
162
|
+
// The Phase Flow diagram in the body must show: ISOLATE → ... → FEEDBACK → SHIP → LAND → USER ACCEPTANCE → CLEANUP
|
|
163
|
+
const flowMatch = skillContent.match(/ISOLATE →.*CLEANUP/);
|
|
164
|
+
if (!flowMatch) {
|
|
165
|
+
throw new Error('Phase Flow diagram not found in SKILL.md body');
|
|
166
|
+
}
|
|
167
|
+
const flow = flowMatch[0];
|
|
168
|
+
|
|
169
|
+
// Verify FEEDBACK comes before SHIP (not after USER ACCEPTANCE)
|
|
170
|
+
const feedbackPos = flow.indexOf('FEEDBACK');
|
|
171
|
+
const shipPos = flow.indexOf('SHIP');
|
|
172
|
+
const userAcceptPos = flow.indexOf('USER ACCEPT');
|
|
173
|
+
if (feedbackPos === -1 || shipPos === -1 || userAcceptPos === -1) {
|
|
174
|
+
throw new Error('Phase Flow diagram missing required phases');
|
|
175
|
+
}
|
|
176
|
+
if (feedbackPos > shipPos) {
|
|
177
|
+
throw new Error('FEEDBACK must come before SHIP in phase flow diagram');
|
|
178
|
+
}
|
|
179
|
+
if (shipPos > userAcceptPos) {
|
|
180
|
+
throw new Error('SHIP must come before USER ACCEPTANCE in phase flow diagram');
|
|
181
|
+
}
|
|
182
|
+
console.log(' ✓ Phase Flow diagram has correct ordering');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function testPhaseFlowConsistencySectionExists() {
|
|
186
|
+
if (!skillContent.includes('Phase Flow Consistency')) {
|
|
187
|
+
throw new Error('SKILL.md must contain "Phase Flow Consistency" section');
|
|
188
|
+
}
|
|
189
|
+
console.log(' ✓ Phase Flow Consistency section exists');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function testCanonicalPhaseOrderTableExists() {
|
|
193
|
+
if (!skillContent.includes('Canonical Phase Order')) {
|
|
194
|
+
throw new Error('SKILL.md must contain "Canonical Phase Order" table');
|
|
195
|
+
}
|
|
196
|
+
console.log(' ✓ Canonical Phase Order table exists');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function testAll11PhasesInPhaseFlowConsistency() {
|
|
200
|
+
const section = skillContent.split('Phase Flow Consistency')[1];
|
|
201
|
+
const requiredPhases = [
|
|
202
|
+
'ISOLATE', 'AUTO-ESTIMATE', 'THINK', 'PLAN', 'BUILD',
|
|
203
|
+
'REVIEW', 'FEEDBACK', 'SHIP', 'LAND', 'USER ACCEPTANCE', 'CLEANUP',
|
|
204
|
+
];
|
|
205
|
+
for (const phase of requiredPhases) {
|
|
206
|
+
if (!section.includes(phase)) {
|
|
207
|
+
throw new Error(`Phase Flow Consistency section missing phase: ${phase}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
console.log(' ✓ All 11 phases referenced in Phase Flow Consistency section');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// === Force Levels Tests ===
|
|
214
|
+
|
|
215
|
+
function testForceLevelsDocumentExists() {
|
|
216
|
+
const forceLevelsPath = path.join(REFERENCES_DIR, 'force-levels.md');
|
|
217
|
+
if (!fs.existsSync(forceLevelsPath)) {
|
|
218
|
+
throw new Error('references/force-levels.md must exist');
|
|
219
|
+
}
|
|
220
|
+
const content = fs.readFileSync(forceLevelsPath, 'utf-8');
|
|
221
|
+
if (!content.includes('轻量') || !content.includes('标准') || !content.includes('复杂')) {
|
|
222
|
+
throw new Error('force-levels.md must document lightweight/standard/complex levels');
|
|
223
|
+
}
|
|
224
|
+
console.log(' ✓ force-levels.md exists and documents three levels');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function testForceLevelsRequiresDelphi() {
|
|
228
|
+
const forceLevelsContent = fs.readFileSync(
|
|
229
|
+
path.join(REFERENCES_DIR, 'force-levels.md'), 'utf-8'
|
|
230
|
+
);
|
|
231
|
+
if (!forceLevelsContent.includes('Delphi') && !forceLevelsContent.includes('delphi')) {
|
|
232
|
+
throw new Error('force-levels.md must reference Delphi review requirement');
|
|
233
|
+
}
|
|
234
|
+
console.log(' ✓ force-levels.md requires Delphi review for all levels');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// === Value Proposition Tests ===
|
|
238
|
+
|
|
239
|
+
function testUniqueValuePropositionExists() {
|
|
240
|
+
if (!skillContent.includes('Unique Value Proposition')) {
|
|
241
|
+
throw new Error('SKILL.md must contain "Unique Value Proposition" section');
|
|
242
|
+
}
|
|
243
|
+
console.log(' ✓ Unique Value Proposition section exists');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function testUvpMentionsTokenSavings() {
|
|
247
|
+
const section = skillContent.split('Unique Value Proposition')[1];
|
|
248
|
+
if (!section.includes('40') || !section.includes('67') || !section.includes('token')) {
|
|
249
|
+
throw new Error('Unique Value Proposition must mention 40-67% token savings');
|
|
250
|
+
}
|
|
251
|
+
console.log(' ✓ UVP mentions 40-67% token savings');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function testUvpMentionsHardGate() {
|
|
255
|
+
const section = skillContent.split('Unique Value Proposition')[1];
|
|
256
|
+
if (!section.includes('HARD-GATE')) {
|
|
257
|
+
throw new Error('Unique Value Proposition must mention HARD-GATE discipline');
|
|
258
|
+
}
|
|
259
|
+
console.log(' ✓ UVP mentions HARD-GATE discipline');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function testUvpMentionsEmergentRequirements() {
|
|
263
|
+
const section = skillContent.split('Unique Value Proposition')[1];
|
|
264
|
+
if (!section.toLowerCase().includes('emergent')) {
|
|
265
|
+
throw new Error('Unique Value Proposition must mention emergent requirements');
|
|
266
|
+
}
|
|
267
|
+
console.log(' ✓ UVP mentions emergent requirements');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// === Phase Timing Tests ===
|
|
271
|
+
|
|
272
|
+
function testTimingSectionExists() {
|
|
273
|
+
const phase2BuildContent = fs.readFileSync(PHASE_2_BUILD_PATH, 'utf-8');
|
|
274
|
+
if (!phase2BuildContent.includes('Timing & Stability')) {
|
|
275
|
+
throw new Error('phase-2-build.md must contain "Timing & Stability" section');
|
|
276
|
+
}
|
|
277
|
+
console.log(' ✓ Timing & Stability section exists in phase-2-build.md');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function testTimeoutRecommendationsExist() {
|
|
281
|
+
const phase2BuildContent = fs.readFileSync(PHASE_2_BUILD_PATH, 'utf-8');
|
|
282
|
+
const timeoutSection = phase2BuildContent.split('Timing & Stability')[1];
|
|
283
|
+
if (!timeoutSection || !timeoutSection.includes('Timeout')) {
|
|
284
|
+
throw new Error('Timing & Stability section must include timeout recommendations');
|
|
285
|
+
}
|
|
286
|
+
console.log(' ✓ Timeout recommendations exist');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function testExecutionTimeEstimatesExist() {
|
|
290
|
+
const phase2BuildContent = fs.readFileSync(PHASE_2_BUILD_PATH, 'utf-8');
|
|
291
|
+
if (!phase2BuildContent.includes('Expected Time')) {
|
|
292
|
+
throw new Error('phase-2-build.md must include expected execution time estimates');
|
|
293
|
+
}
|
|
294
|
+
console.log(' ✓ Expected execution time estimates exist');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// === Orchestration Rules Tests ===
|
|
298
|
+
|
|
299
|
+
function testOrchestrationRulesExists() {
|
|
300
|
+
if (!fs.existsSync(ORCHESTRATION_RULES_PATH)) {
|
|
301
|
+
throw new Error('references/orchestration-rules.md must exist');
|
|
302
|
+
}
|
|
303
|
+
console.log(' ✓ orchestration-rules.md exists');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function testPhaseSubagentMatrixOrder() {
|
|
307
|
+
const orchContent = fs.readFileSync(ORCHESTRATION_RULES_PATH, 'utf-8');
|
|
308
|
+
const expectedOrder = [
|
|
309
|
+
'ISOLATE', 'AUTO-ESTIMATE', 'THINK', 'PLAN', 'BUILD',
|
|
310
|
+
'REVIEW', 'USER ACCEPT', 'FEEDBACK', 'SHIP', 'LAND', 'CLEANUP',
|
|
311
|
+
];
|
|
312
|
+
// The matrix order reflects file names, but phases should all be present
|
|
313
|
+
for (const phase of ['ISOLATE', 'THINK', 'PLAN', 'BUILD', 'REVIEW', 'CLEANUP']) {
|
|
314
|
+
if (!orchContent.includes(phase)) {
|
|
315
|
+
throw new Error(`orchestration-rules.md missing phase: ${phase}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
console.log(' ✓ Orchestration rules covers all key phases');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// === Run All Tests ===
|
|
322
|
+
|
|
323
|
+
function runTests(opts = {}) {
|
|
324
|
+
const { exitOnFail = false } = opts;
|
|
325
|
+
let passed = 0;
|
|
326
|
+
let failed = 0;
|
|
327
|
+
const errors = [];
|
|
328
|
+
|
|
329
|
+
const tests = [
|
|
330
|
+
{ name: 'Positive triggers are present', fn: testPositiveTriggersArePresent },
|
|
331
|
+
{ name: 'Negative triggers have >= 8 entries', fn: testNegativeTriggersArePresent },
|
|
332
|
+
{ name: 'Negative trigger phrases verified', fn: testNegativeTriggerPhrases },
|
|
333
|
+
{ name: 'Positive trigger phrases verified', fn: testPositiveTriggerPhrases },
|
|
334
|
+
{ name: 'Negative test cases exist (>= 11)', fn: testNegativeTestCasesExist },
|
|
335
|
+
{ name: 'workflow_steps order matches canonical', fn: testWorkflowStepsOrder },
|
|
336
|
+
{ name: 'Phase Flow diagram has correct ordering', fn: testPhaseFlowDiagramOrder },
|
|
337
|
+
{ name: 'Phase Flow Consistency section exists', fn: testPhaseFlowConsistencySectionExists },
|
|
338
|
+
{ name: 'Canonical Phase Order table exists', fn: testCanonicalPhaseOrderTableExists },
|
|
339
|
+
{ name: 'All 11 phases in consistency section', fn: testAll11PhasesInPhaseFlowConsistency },
|
|
340
|
+
{ name: 'force-levels.md exists with three levels', fn: testForceLevelsDocumentExists },
|
|
341
|
+
{ name: 'force-levels.md requires Delphi review', fn: testForceLevelsRequiresDelphi },
|
|
342
|
+
{ name: 'Unique Value Proposition exists', fn: testUniqueValuePropositionExists },
|
|
343
|
+
{ name: 'UVP mentions 40-67% token savings', fn: testUvpMentionsTokenSavings },
|
|
344
|
+
{ name: 'UVP mentions HARD-GATE', fn: testUvpMentionsHardGate },
|
|
345
|
+
{ name: 'UVP mentions emergent requirements', fn: testUvpMentionsEmergentRequirements },
|
|
346
|
+
{ name: 'Timing & Stability section exists', fn: testTimingSectionExists },
|
|
347
|
+
{ name: 'Timeout recommendations exist', fn: testTimeoutRecommendationsExist },
|
|
348
|
+
{ name: 'Execution time estimates exist', fn: testExecutionTimeEstimatesExist },
|
|
349
|
+
{ name: 'orchestration-rules.md exists', fn: testOrchestrationRulesExists },
|
|
350
|
+
{ name: 'Orchestration covers all key phases', fn: testPhaseSubagentMatrixOrder },
|
|
351
|
+
{ name: 'Uncommitted Changes Gate section exists', fn: testUncommittedGateExists },
|
|
352
|
+
{ name: 'Uncommitted gate has escape valve', fn: testUncommittedGateEscapeValve },
|
|
353
|
+
{ name: 'Uncommitted gate has log path', fn: testUncommittedGateLogPath },
|
|
354
|
+
];
|
|
355
|
+
|
|
356
|
+
loadSkillMd();
|
|
357
|
+
|
|
358
|
+
for (const { name, fn } of tests) {
|
|
359
|
+
try {
|
|
360
|
+
fn();
|
|
361
|
+
passed++;
|
|
362
|
+
} catch (e) {
|
|
363
|
+
failed++;
|
|
364
|
+
errors.push(`FAIL: ${name}: ${e.message}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
console.log(`\n=== Results: ${passed} passed, ${failed} failed ===\n`);
|
|
369
|
+
errors.forEach(e => console.log(e));
|
|
370
|
+
|
|
371
|
+
if (exitOnFail && failed > 0) {
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
return failed > 0 ? 1 : 0;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Uncommitted Changes Gate tests (must run after the section is added)
|
|
378
|
+
function testUncommittedGateExists() {
|
|
379
|
+
const phase2BuildContent = fs.readFileSync(PHASE_2_BUILD_PATH, 'utf-8');
|
|
380
|
+
if (!phase2BuildContent.includes('Uncommitted Changes Gate')) {
|
|
381
|
+
throw new Error('phase-2-build.md must contain "Uncommitted Changes Gate" section');
|
|
382
|
+
}
|
|
383
|
+
console.log(' ✓ Uncommitted Changes Gate section exists');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function testUncommittedGateEscapeValve() {
|
|
387
|
+
const phase2BuildContent = fs.readFileSync(PHASE_2_BUILD_PATH, 'utf-8');
|
|
388
|
+
if (!phase2BuildContent.includes('SKIP_UNCOMMITTED_GATE')) {
|
|
389
|
+
throw new Error('Uncommitted Changes Gate must provide SKIP_UNCOMMITTED_GATE escape valve');
|
|
390
|
+
}
|
|
391
|
+
console.log(' ✓ Uncommitted gate has SKIP_UNCOMMITTED_GATE escape valve');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function testUncommittedGateLogPath() {
|
|
395
|
+
const phase2BuildContent = fs.readFileSync(PHASE_2_BUILD_PATH, 'utf-8');
|
|
396
|
+
if (!phase2BuildContent.includes('.sprint-state/uncommitted-gate-log.json')) {
|
|
397
|
+
throw new Error('Uncommitted Changes Gate must log to .sprint-state/uncommitted-gate-log.json');
|
|
398
|
+
}
|
|
399
|
+
console.log(' ✓ Uncommitted gate logs to .sprint-state/uncommitted-gate-log.json');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// When run directly (node script.js), call with exitOnFail for CLI behavior.
|
|
403
|
+
if (require.main === module) {
|
|
404
|
+
const exitCode = runTests({ exitOnFail: true });
|
|
405
|
+
process.exit(exitCode);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Vitest discovery — wrap each test function as a vitest it() block
|
|
409
|
+
describe('sprint-flow SKILL.md', () => {
|
|
410
|
+
beforeAll(() => { loadSkillMd(); });
|
|
411
|
+
|
|
412
|
+
const tests = [
|
|
413
|
+
{ name: 'Positive triggers are present', fn: testPositiveTriggersArePresent },
|
|
414
|
+
{ name: 'Negative triggers have >= 8 entries', fn: testNegativeTriggersArePresent },
|
|
415
|
+
{ name: 'Negative trigger phrases verified', fn: testNegativeTriggerPhrases },
|
|
416
|
+
{ name: 'Positive trigger phrases verified', fn: testPositiveTriggerPhrases },
|
|
417
|
+
{ name: 'Negative test cases exist (>= 11)', fn: testNegativeTestCasesExist },
|
|
418
|
+
{ name: 'workflow_steps order matches canonical', fn: testWorkflowStepsOrder },
|
|
419
|
+
{ name: 'Phase Flow diagram has correct ordering', fn: testPhaseFlowDiagramOrder },
|
|
420
|
+
{ name: 'Phase Flow Consistency section exists', fn: testPhaseFlowConsistencySectionExists },
|
|
421
|
+
{ name: 'Canonical Phase Order table exists', fn: testCanonicalPhaseOrderTableExists },
|
|
422
|
+
{ name: 'All 11 phases in consistency section', fn: testAll11PhasesInPhaseFlowConsistency },
|
|
423
|
+
{ name: 'force-levels.md exists with three levels', fn: testForceLevelsDocumentExists },
|
|
424
|
+
{ name: 'force-levels.md requires Delphi review', fn: testForceLevelsRequiresDelphi },
|
|
425
|
+
{ name: 'Unique Value Proposition exists', fn: testUniqueValuePropositionExists },
|
|
426
|
+
{ name: 'UVP mentions 40-67% token savings', fn: testUvpMentionsTokenSavings },
|
|
427
|
+
{ name: 'UVP mentions HARD-GATE', fn: testUvpMentionsHardGate },
|
|
428
|
+
{ name: 'UVP mentions emergent requirements', fn: testUvpMentionsEmergentRequirements },
|
|
429
|
+
{ name: 'Timing & Stability section exists', fn: testTimingSectionExists },
|
|
430
|
+
{ name: 'Timeout recommendations exist', fn: testTimeoutRecommendationsExist },
|
|
431
|
+
{ name: 'Execution time estimates exist', fn: testExecutionTimeEstimatesExist },
|
|
432
|
+
{ name: 'orchestration-rules.md exists', fn: testOrchestrationRulesExists },
|
|
433
|
+
{ name: 'Orchestration covers all key phases', fn: testPhaseSubagentMatrixOrder },
|
|
434
|
+
{ name: 'Uncommitted Changes Gate section exists', fn: testUncommittedGateExists },
|
|
435
|
+
{ name: 'Uncommitted gate has escape valve', fn: testUncommittedGateEscapeValve },
|
|
436
|
+
{ name: 'Uncommitted gate has log path', fn: testUncommittedGateLogPath },
|
|
437
|
+
];
|
|
438
|
+
|
|
439
|
+
for (const { name, fn } of tests) {
|
|
440
|
+
it(name, () => { fn(); });
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
module.exports = { runTests };
|
|
@@ -20,6 +20,65 @@
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Uncommitted Changes Gate
|
|
26
|
+
|
|
27
|
+
**Purpose**: Prevent entering BUILD with uncommitted changes that could mix with sprint work.
|
|
28
|
+
|
|
29
|
+
**Execution**: Before entering Phase 2 BUILD, the orchestrator MUST check for uncommitted changes in the worktree.
|
|
30
|
+
|
|
31
|
+
### Gate Logic
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Check for uncommitted changes
|
|
35
|
+
UNCOMMITTED=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
36
|
+
|
|
37
|
+
if [ "$SKIP_UNCOMMITTED_GATE" = "1" ]; then
|
|
38
|
+
echo "[UNCOMMITTED-GATE] Skipped (SKIP_UNCOMMITTED_GATE=1)"
|
|
39
|
+
echo "{\"skipped\":true,\"reason\":\"SKIP_UNCOMMITTED_GATE=1\",\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > .sprint-state/uncommitted-gate-log.json
|
|
40
|
+
elif [ "$UNCOMMITTED" -gt 0 ]; then
|
|
41
|
+
echo "⚠️ [UNCOMMITTED-GATE] Found ${UNCOMMITTED} uncommitted files in worktree:"
|
|
42
|
+
git status --short 2>/dev/null | head -20
|
|
43
|
+
echo ""
|
|
44
|
+
echo "Uncommitted changes may conflict with sprint work. Recommended actions:"
|
|
45
|
+
echo " 1. Commit changes: git add -A && git commit -m 'pre-sprint: save work before BUILD'"
|
|
46
|
+
echo " 2. Stash changes: git stash push -m 'pre-sprint stash'"
|
|
47
|
+
echo " 3. Skip gate: export SKIP_UNCOMMITTED_GATE=1 (not recommended)"
|
|
48
|
+
echo ""
|
|
49
|
+
echo "Logging to .sprint-state/uncommitted-gate-log.json"
|
|
50
|
+
echo "{\"blocked\":true,\"uncommitted_files\":${UNCOMMITTED},\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > .sprint-state/uncommitted-gate-log.json
|
|
51
|
+
echo "[BLOCK] Uncommitted changes detected. Please commit, stash, or set SKIP_UNCOMMITTED_GATE=1 to continue."
|
|
52
|
+
exit 1
|
|
53
|
+
else
|
|
54
|
+
echo "✅ [UNCOMMITTED-GATE] Worktree clean. Proceeding to BUILD."
|
|
55
|
+
echo "{\"clean\":true,\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > .sprint-state/uncommitted-gate-log.json
|
|
56
|
+
fi
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Escape Valve
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Skip the uncommitted changes gate (use with caution)
|
|
63
|
+
SKIP_UNCOMMITTED_GATE=1
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Log Format (`.sprint-state/uncommitted-gate-log.json`)
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"clean": true,
|
|
71
|
+
"blocked": false,
|
|
72
|
+
"skipped": false,
|
|
73
|
+
"uncommitted_files": 0,
|
|
74
|
+
"timestamp": "2026-07-05T10:00:00Z"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Log location**: `.sprint-state/uncommitted-gate-log.json` — written on every gate execution for audit trail.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
23
82
|
## TDD 强制执行
|
|
24
83
|
|
|
25
84
|
### Gate 5a-BLOCK: 新增文件测试强制
|
|
@@ -41,3 +100,48 @@
|
|
|
41
100
|
# 非 main/master 分支临时跳过 Gate 5a-BLOCK
|
|
42
101
|
SKIP_GATE_5A_BLOCK=1 git commit -m "message"
|
|
43
102
|
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Timing & Stability
|
|
107
|
+
|
|
108
|
+
This section documents expected execution times and timeout handling for each Phase 2 sub-step to reduce execution timing stddev and prevent pipeline stalls.
|
|
109
|
+
|
|
110
|
+
### Expected Execution Times
|
|
111
|
+
|
|
112
|
+
| Step | Description | Expected Time | Timeout | On Timeout |
|
|
113
|
+
|------|-------------|--------------|---------|------------|
|
|
114
|
+
| DELPHI-GATE | Verify delphi-reviewed.json exists | 1-2s | 10s | BLOCK (critical gate) |
|
|
115
|
+
| ralph-loop (per REQ) | TDD + verification per requirement | 5-15 min | 30 min/REQ | Mark REQ as `timeout`, continue next REQ |
|
|
116
|
+
| parallel dispatch | Multi-agent parallel build | 10-30 min | 45 min | Collect partial results, continue |
|
|
117
|
+
| TDD cycle (per unit) | RED → GREEN → REFACTOR | 2-5 min | 10 min | Skip unit, log failure |
|
|
118
|
+
| freeze + blind-review | Code review in isolation | 5-10 min | 20 min | WARNING, continue |
|
|
119
|
+
| verification | Test suite + coverage check | 2-5 min | 15 min | Retry once, then BLOCK |
|
|
120
|
+
| cost monitor | Token cost accounting | <1s | 5s | Skip, log warning |
|
|
121
|
+
| Phase 2 total (lightweight) | ≤3 REQs | 15-30 min | 45 min | — |
|
|
122
|
+
| Phase 2 total (standard) | 4-10 REQs | 30-120 min | 150 min | — |
|
|
123
|
+
| Phase 2 total (complex) | >10 REQs | 60-240 min | 300 min | — |
|
|
124
|
+
|
|
125
|
+
### Stability Guidelines
|
|
126
|
+
|
|
127
|
+
1. **Timeout handling**: All Phase 2 sub-steps MUST have explicit timeouts. If a step times out, log the failure to `.sprint-state/phase-outputs/phase-2-errors.json` and continue to the next step (except DELPHI-GATE which is a hard BLOCK).
|
|
128
|
+
|
|
129
|
+
2. **Retry strategy**: For recoverable failures (verification, TDD cycle):
|
|
130
|
+
- First failure: log warning, retry once
|
|
131
|
+
- Second failure: log error, BLOCK and prompt user decision
|
|
132
|
+
- Do NOT retry more than twice for any single sub-step
|
|
133
|
+
|
|
134
|
+
3. **Parallel dispatch stability**: When using `--mode parallel`, if any agent fails:
|
|
135
|
+
- Collect partial results from successful agents
|
|
136
|
+
- Rerun failed agent(s) individually with `--mode ralph-loop`
|
|
137
|
+
- Do NOT rerun the entire parallel batch
|
|
138
|
+
|
|
139
|
+
4. **Cost monitor thresholds**:
|
|
140
|
+
- Token cost per REQ > 50,000 → WARNING (review REQ scope)
|
|
141
|
+
- Token cost per REQ > 100,000 → BLOCK (REQ too large, split into smaller units)
|
|
142
|
+
|
|
143
|
+
5. **StdDev reduction**: To reduce execution timing variability:
|
|
144
|
+
- Cache dependency installation results between REQs
|
|
145
|
+
- Reuse TDD scaffolding across similar REQ types
|
|
146
|
+
- Pre-compute code structure analysis once at Phase start
|
|
147
|
+
- Batch lint/format operations at the end, not per REQ
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-06
|
|
4
|
+
**Commit:** 132a12e
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.12.
|
|
6
|
+
**Version:** 0.12.12.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:**
|
|
3
|
+
**Generated:** 2026-07-06
|
|
4
|
+
**Commit:** 132a12e
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.12.
|
|
6
|
+
**Version:** 0.12.12.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:**
|
|
3
|
+
**Generated:** 2026-07-06
|
|
4
|
+
**Commit:** 132a12e
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.12.
|
|
6
|
+
**Version:** 0.12.12.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**11-phase** development pipeline: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → SHIP → LAND → USER ACCEPTANCE → FEEDBACK → 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.
|