xp-gate 0.5.1

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 (90) hide show
  1. package/adapter-common.sh +192 -0
  2. package/adapters/cpp.sh +76 -0
  3. package/adapters/dart.sh +41 -0
  4. package/adapters/flutter.sh +41 -0
  5. package/adapters/go.sh +59 -0
  6. package/adapters/iac.sh +189 -0
  7. package/adapters/java.sh +191 -0
  8. package/adapters/kotlin.sh +77 -0
  9. package/adapters/objectivec.sh +38 -0
  10. package/adapters/powershell.sh +138 -0
  11. package/adapters/python.sh +104 -0
  12. package/adapters/shell.sh +55 -0
  13. package/adapters/swift.sh +44 -0
  14. package/adapters/typescript.sh +61 -0
  15. package/bin/xp-gate.js +157 -0
  16. package/hooks/adapter-common.sh +192 -0
  17. package/hooks/pre-commit +1667 -0
  18. package/hooks/pre-push +395 -0
  19. package/lib/__tests__/detect-deps.test.js +209 -0
  20. package/lib/__tests__/doctor.test.js +448 -0
  21. package/lib/__tests__/download-skill.test.js +281 -0
  22. package/lib/__tests__/init.test.js +327 -0
  23. package/lib/__tests__/install-skill.test.js +326 -0
  24. package/lib/__tests__/migrate.test.js +212 -0
  25. package/lib/__tests__/rollback.test.js +183 -0
  26. package/lib/__tests__/ui-detector.test.ts +200 -0
  27. package/lib/__tests__/uninstall-skill.test.js +189 -0
  28. package/lib/__tests__/uninstall.test.js +589 -0
  29. package/lib/__tests__/update-skill.test.js +276 -0
  30. package/lib/detect-deps.js +157 -0
  31. package/lib/doctor.js +370 -0
  32. package/lib/download-skill.js +96 -0
  33. package/lib/init.js +367 -0
  34. package/lib/install-skill.js +184 -0
  35. package/lib/migrate.js +120 -0
  36. package/lib/rollback.js +78 -0
  37. package/lib/ui-detector.ts +99 -0
  38. package/lib/uninstall-skill.js +69 -0
  39. package/lib/uninstall.js +401 -0
  40. package/lib/update-skill.js +90 -0
  41. package/package.json +39 -0
  42. package/plugins/claude-code/.claude-plugin/plugin.json +21 -0
  43. package/plugins/claude-code/bin/delphi-review-guard.sh +68 -0
  44. package/plugins/claude-code/bin/xp-gate-check +47 -0
  45. package/plugins/claude-code/hooks/hooks.json +37 -0
  46. package/skills/delphi-review/.delphi-config.json.example +45 -0
  47. package/skills/delphi-review/AGENTS.md +54 -0
  48. package/skills/delphi-review/INSTALL.md +152 -0
  49. package/skills/delphi-review/SKILL.md +371 -0
  50. package/skills/delphi-review/evals/evals.json +82 -0
  51. package/skills/delphi-review/opencode.json.delphi.example +56 -0
  52. package/skills/delphi-review/references/code-walkthrough.md +486 -0
  53. package/skills/ralph-loop/SKILL.md +330 -0
  54. package/skills/ralph-loop/evals/evals.json +311 -0
  55. package/skills/ralph-loop/evolution-history.json +59 -0
  56. package/skills/ralph-loop/evolution-log.md +16 -0
  57. package/skills/ralph-loop/references/components/memory.md +55 -0
  58. package/skills/ralph-loop/references/components/middleware.md +54 -0
  59. package/skills/ralph-loop/references/components/skill-invocations.md +39 -0
  60. package/skills/ralph-loop/references/components/system-prompt.md +24 -0
  61. package/skills/ralph-loop/references/components/tool-descriptions.md +32 -0
  62. package/skills/ralph-loop/references/phase-2-build-ralph.md +89 -0
  63. package/skills/ralph-loop/templates/progress-log.md +36 -0
  64. package/skills/sprint-flow/SKILL.md +600 -0
  65. package/skills/sprint-flow/evals/evals.json +78 -0
  66. package/skills/sprint-flow/evolution-history.json +39 -0
  67. package/skills/sprint-flow/evolution-log.md +23 -0
  68. package/skills/sprint-flow/references/components/memory.md +87 -0
  69. package/skills/sprint-flow/references/components/middleware.md +72 -0
  70. package/skills/sprint-flow/references/components/skill-invocations.md +104 -0
  71. package/skills/sprint-flow/references/components/system-prompt.md +27 -0
  72. package/skills/sprint-flow/references/components/tool-descriptions.md +96 -0
  73. package/skills/sprint-flow/references/phase-0-think.md +115 -0
  74. package/skills/sprint-flow/references/phase-1-plan.md +178 -0
  75. package/skills/sprint-flow/references/phase-2-build.md +198 -0
  76. package/skills/sprint-flow/references/phase-3-review.md +213 -0
  77. package/skills/sprint-flow/references/phase-4-uat.md +125 -0
  78. package/skills/sprint-flow/references/phase-5-feedback.md +100 -0
  79. package/skills/sprint-flow/references/phase-6-ship.md +193 -0
  80. package/skills/sprint-flow/references/phase-7-land.md +140 -0
  81. package/skills/sprint-flow/references/phase-8-cleanup.md +192 -0
  82. package/skills/sprint-flow/templates/emergent-issues-template.md +120 -0
  83. package/skills/sprint-flow/templates/pain-document-template.md +115 -0
  84. package/skills/sprint-flow/templates/sprint-summary-template.md +120 -0
  85. package/skills/test-specification-alignment/AGENTS.md +59 -0
  86. package/skills/test-specification-alignment/SKILL.md +605 -0
  87. package/skills/test-specification-alignment/evals/evals.json +75 -0
  88. package/skills/test-specification-alignment/references/alignment-verification-algorithm.md +493 -0
  89. package/skills/test-specification-alignment/references/phase2-constraint-enforcement.md +431 -0
  90. package/skills/test-specification-alignment/references/specification-format.md +348 -0
@@ -0,0 +1,493 @@
1
+ # 对齐验证算法
2
+
3
+ ## 核心原则
4
+
5
+ **使用确定性算法验证对齐,不依赖 LLM 语义理解。**
6
+
7
+ ---
8
+
9
+ ## 算法实现
10
+
11
+ ### Step 1: 解析 Specification
12
+
13
+ ```typescript
14
+ interface SpecificationMap {
15
+ requirements: Requirement[];
16
+ designDecisions: DesignDecision[];
17
+ apiContracts: ApiContract[];
18
+ }
19
+
20
+ interface Requirement {
21
+ id: string; // REQ-AUTH-001
22
+ description: string;
23
+ acceptanceCriteria: string[]; // ["AC-AUTH-001-01", "AC-AUTH-001-02"]
24
+ edgeCases: string[];
25
+ securityConsiderations: string[];
26
+ }
27
+
28
+ function parseSpecification(specPath: string): SpecificationMap {
29
+ // 使用 YAML parser,不依赖 LLM
30
+ const content = fs.readFileSync(specPath, 'utf-8');
31
+ const spec = yaml.parse(content);
32
+
33
+ return {
34
+ requirements: spec.requirements.map(r => ({
35
+ id: r.id,
36
+ description: r.description,
37
+ acceptanceCriteria: r.acceptance_criteria?.map(ac => ac.id) || [],
38
+ edgeCases: r.edge_cases || [],
39
+ securityConsiderations: r.security_considerations || []
40
+ })),
41
+ designDecisions: spec.design_decisions?.map(d => d.id) || [],
42
+ apiContracts: spec.api_contracts?.map(a => a.endpoint) || []
43
+ };
44
+ }
45
+ ```
46
+
47
+ ---
48
+
49
+ ### Step 2: 解析测试文件
50
+
51
+ ```typescript
52
+ interface TestMap {
53
+ tests: TestCase[];
54
+ totalTests: number;
55
+ totalAssertions: number;
56
+ }
57
+
58
+ interface TestCase {
59
+ name: string;
60
+ file: string;
61
+ requirementId: string | null; // 从 @test 标签提取
62
+ intent: string | null; // 从 @intent 标签提取
63
+ covers: string[]; // 从 @covers 标签提取
64
+ edgeCases: string[]; // 从 @edge_cases 标签提取
65
+ assertions: number; // 断言数量
66
+ }
67
+
68
+ function parseTestFiles(testPaths: string[]): TestMap {
69
+ const tests: TestCase[] = [];
70
+ let totalAssertions = 0;
71
+
72
+ for (const path of testPaths) {
73
+ const content = fs.readFileSync(path, 'utf-8');
74
+
75
+ // 根据文件类型选择解析器
76
+ if (path.endsWith('.ts') || path.endsWith('.js')) {
77
+ const result = parseTypeScriptTests(content, path);
78
+ tests.push(...result.tests);
79
+ totalAssertions += result.assertions;
80
+ } else if (path.endsWith('.py')) {
81
+ const result = parsePythonTests(content, path);
82
+ tests.push(...result.tests);
83
+ totalAssertions += result.assertions;
84
+ } else if (path.endsWith('.go')) {
85
+ const result = parseGoTests(content, path);
86
+ tests.push(...result.tests);
87
+ totalAssertions += result.assertions;
88
+ }
89
+ }
90
+
91
+ return { tests, totalTests: tests.length, totalAssertions };
92
+ }
93
+
94
+ // TypeScript/JavaScript 解析
95
+ function parseTypeScriptTests(content: string, filePath: string): { tests: TestCase[], assertions: number } {
96
+ const tests: TestCase[] = [];
97
+ let totalAssertions = 0;
98
+
99
+ // 使用正则提取 JSDoc 注释和测试函数
100
+ const testPattern = /\/\*\*([\s\S]*?)\*\/\s*(test|it|describe)\s*\(\s*['"`]([^'"`]+)['"`]/g;
101
+
102
+ let match;
103
+ while ((match = testPattern.exec(content)) !== null) {
104
+ const jsdoc = match[1];
105
+ const testName = match[3];
106
+
107
+ // 提取标签
108
+ const requirementId = extractTag(jsdoc, '@test');
109
+ const intent = extractTag(jsdoc, '@intent');
110
+ const covers = extractAllTags(jsdoc, '@covers');
111
+ const edgeCases = extractAllTags(jsdoc, '@edge_cases');
112
+
113
+ // 计算断言数量 (expect, assert, should)
114
+ const testBody = extractTestBody(content, match.index);
115
+ const assertions = countAssertions(testBody);
116
+ totalAssertions += assertions;
117
+
118
+ tests.push({
119
+ name: testName,
120
+ file: filePath,
121
+ requirementId,
122
+ intent,
123
+ covers,
124
+ edgeCases,
125
+ assertions
126
+ });
127
+ }
128
+
129
+ return { tests, assertions: totalAssertions };
130
+ }
131
+
132
+ // Python 解析
133
+ function parsePythonTests(content: string, filePath: string): { tests: TestCase[], assertions: number } {
134
+ const tests: TestCase[] = [];
135
+ let totalAssertions = 0;
136
+
137
+ // 提取注释和测试函数
138
+ const testPattern = /(# @test\s+(\S+)[\s\S]*?)def\s+(test_\w+)/g;
139
+
140
+ let match;
141
+ while ((match = testPattern.exec(content)) !== null) {
142
+ const comments = match[1];
143
+ const testName = match[3];
144
+
145
+ const requirementId = extractTagFromComment(comments, '@test');
146
+ const intent = extractTagFromComment(comments, '@intent');
147
+ const covers = extractAllTagsFromComment(comments, '@covers');
148
+ const edgeCases = extractAllTagsFromComment(comments, '@edge_cases');
149
+
150
+ // 计算 assert 语句数量
151
+ const testBody = extractPythonFunctionBody(content, match.index);
152
+ const assertions = (testBody.match(/\bassert\b/g) || []).length;
153
+ totalAssertions += assertions;
154
+
155
+ tests.push({
156
+ name: testName,
157
+ file: filePath,
158
+ requirementId,
159
+ intent,
160
+ covers,
161
+ edgeCases,
162
+ assertions
163
+ });
164
+ }
165
+
166
+ return { tests, assertions: totalAssertions };
167
+ }
168
+
169
+ // Go 解析
170
+ function parseGoTests(content: string, filePath: string): { tests: TestCase[], assertions: number } {
171
+ const tests: TestCase[] = [];
172
+ let totalAssertions = 0;
173
+
174
+ // 提取注释和测试函数
175
+ const testPattern = /(\/\/ @test\s+(\S+)[\s\S]*?)func\s+(Test_\w+)/g;
176
+
177
+ let match;
178
+ while ((match = testPattern.exec(content)) !== null) {
179
+ const comments = match[1];
180
+ const testName = match[3];
181
+
182
+ const requirementId = extractTagFromGoComment(comments, '@test');
183
+ const intent = extractTagFromGoComment(comments, '@intent');
184
+ const covers = extractAllTagsFromGoComment(comments, '@covers');
185
+ const edgeCases = extractAllTagsFromGoComment(comments, '@edge_cases');
186
+
187
+ // 计算 assert/t.Require 数量
188
+ const testBody = extractGoFunctionBody(content, match.index);
189
+ const assertions = (testBody.match(/\bassert\.|t\.Require|t\.Error/g) || []).length;
190
+ totalAssertions += assertions;
191
+
192
+ tests.push({
193
+ name: testName,
194
+ file: filePath,
195
+ requirementId,
196
+ intent,
197
+ covers,
198
+ edgeCases,
199
+ assertions
200
+ });
201
+ }
202
+
203
+ return { tests, assertions: totalAssertions };
204
+ }
205
+ ```
206
+
207
+ ---
208
+
209
+ ### Step 3: 对齐验证
210
+
211
+ ```typescript
212
+ interface AlignmentReport {
213
+ score: number;
214
+ issues: AlignmentIssue[];
215
+ coverage: CoverageReport;
216
+ passed: boolean;
217
+ }
218
+
219
+ interface AlignmentIssue {
220
+ type: 'MISSING_TEST' | 'MISSING_AC_COVERAGE' | 'MISSING_EDGE_CASE'
221
+ | 'MISSING_INTENT' | 'INCORRECT_INTENT' | 'LOW_ASSERTIONS';
222
+ severity: 'Critical' | 'Major' | 'Minor';
223
+ requirementId?: string;
224
+ acId?: string;
225
+ testId?: string;
226
+ message: string;
227
+ }
228
+
229
+ interface CoverageReport {
230
+ requirementsCovered: number;
231
+ requirementsTotal: number;
232
+ acCovered: number;
233
+ acTotal: number;
234
+ edgeCasesCovered: number;
235
+ edgeCasesTotal: number;
236
+ testsWithIntent: number;
237
+ testsTotal: number;
238
+ }
239
+
240
+ function verifyAlignment(
241
+ specMap: SpecificationMap,
242
+ testMap: TestMap
243
+ ): AlignmentReport {
244
+ const issues: AlignmentIssue[] = [];
245
+ const coverage: CoverageReport = {
246
+ requirementsCovered: 0,
247
+ requirementsTotal: specMap.requirements.length,
248
+ acCovered: 0,
249
+ acTotal: 0,
250
+ edgeCasesCovered: 0,
251
+ edgeCasesTotal: 0,
252
+ testsWithIntent: 0,
253
+ testsTotal: testMap.totalTests
254
+ };
255
+
256
+ // 规则 1: 每个 requirement 必须有测试
257
+ for (const req of specMap.requirements) {
258
+ const hasTest = testMap.tests.some(t => t.requirementId === req.id);
259
+ if (!hasTest) {
260
+ issues.push({
261
+ type: 'MISSING_TEST',
262
+ severity: 'Critical',
263
+ requirementId: req.id,
264
+ message: `Requirement ${req.id} 没有对应的测试`
265
+ });
266
+ } else {
267
+ coverage.requirementsCovered++;
268
+ }
269
+
270
+ // 规则 2: 每个 AC 必须有断言覆盖
271
+ for (const acId of req.acceptanceCriteria) {
272
+ coverage.acTotal++;
273
+ const hasCoverage = testMap.tests.some(t => t.covers.includes(acId));
274
+ if (!hasCoverage) {
275
+ issues.push({
276
+ type: 'MISSING_AC_COVERAGE',
277
+ severity: 'Major',
278
+ requirementId: req.id,
279
+ acId,
280
+ message: `Acceptance Criteria ${acId} 没有被测试覆盖`
281
+ });
282
+ } else {
283
+ coverage.acCovered++;
284
+ }
285
+ }
286
+
287
+ // 规则 3: 每个 edge case 应该有测试
288
+ for (const edgeCase of req.edgeCases) {
289
+ coverage.edgeCasesTotal++;
290
+ const hasTest = testMap.tests.some(t => t.edgeCases.includes(edgeCase));
291
+ if (!hasTest) {
292
+ issues.push({
293
+ type: 'MISSING_EDGE_CASE',
294
+ severity: 'Minor',
295
+ requirementId: req.id,
296
+ message: `Edge case "${edgeCase}" 没有对应的测试`
297
+ });
298
+ } else {
299
+ coverage.edgeCasesCovered++;
300
+ }
301
+ }
302
+ }
303
+
304
+ // 规则 4: 每个测试必须有意图声明
305
+ for (const test of testMap.tests) {
306
+ if (!test.intent) {
307
+ issues.push({
308
+ type: 'MISSING_INTENT',
309
+ severity: 'Major',
310
+ testId: test.name,
311
+ message: `Test ${test.name} 缺少 @intent 声明`
312
+ });
313
+ } else {
314
+ coverage.testsWithIntent++;
315
+ }
316
+
317
+ // 规则 5: 每个测试应该有足够的断言
318
+ if (test.assertions < 2) {
319
+ issues.push({
320
+ type: 'LOW_ASSERTIONS',
321
+ severity: 'Minor',
322
+ testId: test.name,
323
+ message: `Test ${test.name} 只有 ${test.assertions} 个断言,可能不够充分`
324
+ });
325
+ }
326
+ }
327
+
328
+ // 计算分数
329
+ const score = calculateScore(issues, coverage);
330
+
331
+ return {
332
+ score,
333
+ issues,
334
+ coverage,
335
+ passed: score >= 80
336
+ };
337
+ }
338
+ ```
339
+
340
+ ---
341
+
342
+ ### Step 4: 分数计算
343
+
344
+ ```typescript
345
+ function calculateScore(issues: AlignmentIssue[], coverage: CoverageReport): number {
346
+ // 维度权重
347
+ const weights = {
348
+ requirementCoverage: 30,
349
+ acCoverage: 25,
350
+ testIntent: 20,
351
+ edgeCaseCoverage: 15,
352
+ testDataValidity: 10
353
+ };
354
+
355
+ // 计算各维度得分
356
+ const reqScore = (coverage.requirementsCovered / coverage.requirementsTotal) * weights.requirementCoverage;
357
+
358
+ const acScore = coverage.acTotal > 0
359
+ ? (coverage.acCovered / coverage.acTotal) * weights.acCoverage
360
+ : weights.acCoverage; // 如果没有 AC,给满分
361
+
362
+ const intentScore = (coverage.testsWithIntent / coverage.testsTotal) * weights.testIntent;
363
+
364
+ const edgeScore = coverage.edgeCasesTotal > 0
365
+ ? (coverage.edgeCasesCovered / coverage.edgeCasesTotal) * weights.edgeCaseCoverage
366
+ : weights.edgeCaseCoverage;
367
+
368
+ // 测试数据有效性基于断言数量
369
+ const avgAssertions = coverage.testsTotal > 0
370
+ ? /* 需要从 testMap 传入 */ 3
371
+ : 0;
372
+ const dataScore = avgAssertions >= 3 ? weights.testDataValidity
373
+ : (avgAssertions / 3) * weights.testDataValidity;
374
+
375
+ // 扣除 Critical/Major 问题分数
376
+ const criticalCount = issues.filter(i => i.severity === 'Critical').length;
377
+ const majorCount = issues.filter(i => i.severity === 'Major').length;
378
+
379
+ const penalty = criticalCount * 5 + majorCount * 2;
380
+
381
+ const totalScore = Math.max(0, reqScore + acScore + intentScore + edgeScore + dataScore - penalty);
382
+
383
+ return Math.round(totalScore * 10) / 10;
384
+ }
385
+ ```
386
+
387
+ ---
388
+
389
+ ## 辅助函数
390
+
391
+ ```typescript
392
+ // 从 JSDoc 提取单个标签
393
+ function extractTag(jsdoc: string, tag: string): string | null {
394
+ const pattern = new RegExp(`@${tag}\\s+(.+?)(?:\\n|\\*/)`);
395
+ const match = jsdoc.match(pattern);
396
+ return match ? match[1].trim() : null;
397
+ }
398
+
399
+ // 从 JSDoc 提取所有标签
400
+ function extractAllTags(jsdoc: string, tag: string): string[] {
401
+ const pattern = new RegExp(`@${tag}\\s+([\\w\\-,\\s]+?)(?:\\n|\\*)`, 'g');
402
+ const matches = [];
403
+ let match;
404
+ while ((match = pattern.exec(jsdoc)) !== null) {
405
+ // 处理逗号分隔的多个值
406
+ const values = match[1].split(',').map(v => v.trim());
407
+ matches.push(...values);
408
+ }
409
+ return matches;
410
+ }
411
+
412
+ // 计算断言数量
413
+ function countAssertions(code: string): number {
414
+ // 匹配 expect(), assert, should
415
+ const patterns = [
416
+ /expect\s*\(/g,
417
+ /assert\.\w+\(/g,
418
+ /\.should\./g
419
+ ];
420
+
421
+ let count = 0;
422
+ for (const pattern of patterns) {
423
+ const matches = code.match(pattern);
424
+ if (matches) count += matches.length;
425
+ }
426
+ return count;
427
+ }
428
+ ```
429
+
430
+ ---
431
+
432
+ ## Legacy 模式算法
433
+
434
+ ```typescript
435
+ function verifyLegacyMode(testMap: TestMap): AlignmentReport {
436
+ // Legacy 模式只验证测试覆盖率,不验证对齐
437
+
438
+ const issues: AlignmentIssue[] = [];
439
+
440
+ // 检查是否有跳过的测试
441
+ const skippedTests = testMap.tests.filter(t => t.name.includes('.skip') || t.name.includes('xit'));
442
+ if (skippedTests.length > 0) {
443
+ issues.push({
444
+ type: 'SKIPPED_TESTS',
445
+ severity: 'Major',
446
+ message: `发现 ${skippedTests.length} 个跳过的测试`
447
+ });
448
+ }
449
+
450
+ // 计算基本分数
451
+ const score = testMap.totalTests > 0 && testMap.totalAssertions > 0 ? 80 : 0;
452
+
453
+ return {
454
+ score,
455
+ issues,
456
+ coverage: {
457
+ requirementsCovered: 0,
458
+ requirementsTotal: 0,
459
+ acCovered: 0,
460
+ acTotal: 0,
461
+ edgeCasesCovered: 0,
462
+ edgeCasesTotal: 0,
463
+ testsWithIntent: testMap.tests.filter(t => t.intent).length,
464
+ testsTotal: testMap.totalTests
465
+ },
466
+ passed: score >= 80 && issues.filter(i => i.severity === 'Critical').length === 0
467
+ };
468
+ }
469
+ ```
470
+
471
+ ---
472
+
473
+ ## 性能优化
474
+
475
+ ```typescript
476
+ // 缓存已解析的文件
477
+ const parseCache = new Map<string, { mtime: number, result: any }>();
478
+
479
+ function parseWithCache<T>(path: string, parser: (content: string) => T): T {
480
+ const stat = fs.statSync(path);
481
+ const cached = parseCache.get(path);
482
+
483
+ if (cached && cached.mtime === stat.mtimeMs) {
484
+ return cached.result;
485
+ }
486
+
487
+ const content = fs.readFileSync(path, 'utf-8');
488
+ const result = parser(content);
489
+
490
+ parseCache.set(path, { mtime: stat.mtimeMs, result });
491
+ return result;
492
+ }
493
+ ```