@boyingliu01/xp-gate 0.35.4 → 0.35.6
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/bin/xp-gate.js +1 -1
- package/gates/test-layers.ts +149 -20
- package/lib/__tests__/check-single-file.test.js +63 -0
- package/lib/__tests__/doctor-brief.test.js +59 -0
- package/lib/doctor-brief.js +159 -0
- package/lib/doctor.js +23 -3
- package/lib/gate-runner.js +49 -3
- package/lib/mattpocock-license-verdict.json +1 -1
- package/mock-policy/AGENTS.md +2 -2
- package/mutation/AGENTS.md +2 -2
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/lib/delphi-run.d.ts +0 -1
- package/plugins/dsh/lib/delphi-run.d.ts.map +1 -1
- package/plugins/dsh/lib/delphi-run.js +55 -5
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/src/delphi-findings.test.ts +109 -0
- package/plugins/dsh/src/delphi-run.ts +53 -6
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
- package/principles/AGENTS.md +2 -2
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/sprint-flow/AGENTS.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +2 -2
package/bin/xp-gate.js
CHANGED
|
@@ -277,7 +277,7 @@ const COMMANDS = {
|
|
|
277
277
|
}
|
|
278
278
|
return doctor(subargs).then(code => process.exit(code));
|
|
279
279
|
},
|
|
280
|
-
usage: 'xp-gate doctor [--fix [--install-tools] | --sync-hooks [--force] | --refresh-env] | --json | --format json (the two machine-readable modes print one JSON document and refuse --fix/--sync-hooks; --refresh-env is read-only so it combines with them)'
|
|
280
|
+
usage: 'xp-gate doctor [--fix [--install-tools] | --sync-hooks [--force] | --refresh-env | --brief] | --json | --format json (--brief is a read-only one-shot fact dump and refuses --fix/--sync-hooks/--install-tools/--force; the two machine-readable modes print one JSON document and refuse --fix/--sync-hooks; --refresh-env is read-only so it combines with them)'
|
|
281
281
|
},
|
|
282
282
|
'ui-review': {
|
|
283
283
|
description: 'Run UI review for non-sprint developers (generates .ui-gate-result.json)',
|
package/gates/test-layers.ts
CHANGED
|
@@ -20,8 +20,23 @@ export interface TestLayerReport {
|
|
|
20
20
|
layer1: {
|
|
21
21
|
pairedSources: number;
|
|
22
22
|
unpairedSources: string[];
|
|
23
|
+
/**
|
|
24
|
+
* Sources the pairing convention does not describe (#642).
|
|
25
|
+
*
|
|
26
|
+
* Reported apart from `unpairedSources` because the two call for opposite
|
|
27
|
+
* responses: an unpaired source needs a test, whereas an out-of-scope source
|
|
28
|
+
* (a shell script covered by BATS) needs the convention widened. Counting it
|
|
29
|
+
* as "unpaired" would ask the reader to write a .test.ts for `pre-commit`.
|
|
30
|
+
*/
|
|
31
|
+
outOfScopeSources: string[];
|
|
23
32
|
pairRate: number;
|
|
24
33
|
};
|
|
34
|
+
/** Test suites that live outside src/ -- BATS and other shell-level suites (#642). */
|
|
35
|
+
shellSuites: {
|
|
36
|
+
total: number;
|
|
37
|
+
bats: number;
|
|
38
|
+
files: string[];
|
|
39
|
+
};
|
|
25
40
|
layer2: {
|
|
26
41
|
integrationFiles: number;
|
|
27
42
|
};
|
|
@@ -175,6 +190,8 @@ interface LayerCounts {
|
|
|
175
190
|
integrationTests: string[];
|
|
176
191
|
e2eTests: string[];
|
|
177
192
|
unpairedSources: string[];
|
|
193
|
+
outOfScopeSources: string[];
|
|
194
|
+
shellSuites: string[];
|
|
178
195
|
pairedCount: number;
|
|
179
196
|
}
|
|
180
197
|
|
|
@@ -189,15 +206,24 @@ function appendPairingLines(messages: string[], counts: LayerCounts, totalNeedin
|
|
|
189
206
|
messages.push(' Layer 1 (Unit Pairing):');
|
|
190
207
|
messages.push(` Paired: ${counts.pairedCount}/${totalNeedingTests} (${pairRate}%)`);
|
|
191
208
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
209
|
+
appendUnpairedLines(messages, counts.unpairedSources);
|
|
210
|
+
|
|
211
|
+
// Out-of-scope sources get their own line: folding them into the unpaired list
|
|
212
|
+
// would ask the reader to write a module test for a shell script (#642).
|
|
213
|
+
if (counts.outOfScopeSources.length > 0) {
|
|
214
|
+
messages.push(` Not pair-able (shell/other): ${counts.outOfScopeSources.length}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Unpaired list, kept short enough not to bury the summary above it (#620). */
|
|
219
|
+
function appendUnpairedLines(messages: string[], unpaired: string[]): void {
|
|
220
|
+
if (unpaired.length === 0) return;
|
|
221
|
+
if (unpaired.length <= UNPAIRED_DISPLAY_LIMIT) {
|
|
222
|
+
messages.push(` Unpaired: ${unpaired.join(', ')}`);
|
|
198
223
|
return;
|
|
199
224
|
}
|
|
200
|
-
messages.push(` Unpaired: ${unpaired.
|
|
225
|
+
messages.push(` Unpaired: ${unpaired.length} files (showing first 10)`);
|
|
226
|
+
unpaired.slice(0, UNPAIRED_DISPLAY_LIMIT).forEach(f => messages.push(` - ${f}`));
|
|
201
227
|
}
|
|
202
228
|
|
|
203
229
|
/** Format the console block for one analysis. */
|
|
@@ -207,10 +233,14 @@ function buildLayerMessages(counts: LayerCounts, totalNeedingTests: number): str
|
|
|
207
233
|
'━━━ Test Layer Analytics ━━━',
|
|
208
234
|
'',
|
|
209
235
|
` Sources: ${counts.sourceFiles.length} files`,
|
|
210
|
-
` Tests: ${counts.testFiles.length} total`,
|
|
236
|
+
` Tests: ${counts.testFiles.length + counts.shellSuites.length} total`,
|
|
211
237
|
` Unit: ${counts.unitTests.length}`,
|
|
212
|
-
` Integra: ${counts.integrationTests.length}`,
|
|
238
|
+
` Integra: ${counts.integrationTests.length + counts.shellSuites.length}`,
|
|
213
239
|
` E2E: ${counts.e2eTests.length}`,
|
|
240
|
+
// Printed separately rather than folded into "Integra": whether a suite is
|
|
241
|
+
// BATS or a TS integration test is a fact about the repo that the reader is
|
|
242
|
+
// here to learn, and a shell suite is never counted as a unit test (#642).
|
|
243
|
+
` Shell: ${counts.shellSuites.length}`,
|
|
214
244
|
'',
|
|
215
245
|
];
|
|
216
246
|
|
|
@@ -236,13 +266,21 @@ function buildLayerMessages(counts: LayerCounts, totalNeedingTests: number): str
|
|
|
236
266
|
function computePairing(
|
|
237
267
|
sourceFiles: string[],
|
|
238
268
|
allFiles: string[],
|
|
239
|
-
): { unpairedSources: string[]; pairedCount: number } {
|
|
269
|
+
): { unpairedSources: string[]; outOfScopeSources: string[]; pairedCount: number } {
|
|
240
270
|
const allSourceRelative = allFiles.map(f => path.join('src', f));
|
|
241
271
|
const unpairedSources: string[] = [];
|
|
272
|
+
const outOfScopeSources: string[] = [];
|
|
242
273
|
let pairedCount = 0;
|
|
243
274
|
|
|
244
275
|
for (const source of sourceFiles) {
|
|
245
276
|
if (isExempt(path.basename(source))) continue;
|
|
277
|
+
// A source the convention does not describe is not "unpaired" -- nobody was
|
|
278
|
+
// ever asked to pair it. Reporting it as unpaired would send the reader to
|
|
279
|
+
// write a module test for a shell script (#642).
|
|
280
|
+
if (isPairingOutOfScope(source)) {
|
|
281
|
+
outOfScopeSources.push(source);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
246
284
|
if (findExpectedTest(path.join('src', source), allSourceRelative)) {
|
|
247
285
|
pairedCount++;
|
|
248
286
|
} else {
|
|
@@ -250,7 +288,95 @@ function computePairing(
|
|
|
250
288
|
}
|
|
251
289
|
}
|
|
252
290
|
|
|
253
|
-
return { unpairedSources, pairedCount };
|
|
291
|
+
return { unpairedSources, outOfScopeSources, pairedCount };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Directories that hold test suites but are not under `src/` (#642).
|
|
296
|
+
*
|
|
297
|
+
* `test-layers` used to scan only `<repo>/src`, so this repository's 166 BATS
|
|
298
|
+
* suites were invisible: the report said "Integration: 0" about a repo whose
|
|
299
|
+
* hook tests spawn real git, real shell adapters and real processes. A metric
|
|
300
|
+
* that omits 37% of the suites cannot be used to judge the test strategy.
|
|
301
|
+
*/
|
|
302
|
+
const SHELL_SUITE_DIRS = [
|
|
303
|
+
'githooks/__tests__',
|
|
304
|
+
'scripts/__tests__',
|
|
305
|
+
'tests',
|
|
306
|
+
'test',
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
/** A shell-level test suite: BATS or a bare .test.cjs/.test.mjs. */
|
|
310
|
+
function isShellSuite(normalized: string): boolean {
|
|
311
|
+
return /\.test\.bats$/.test(normalized)
|
|
312
|
+
|| /\.test\.(cjs|mjs)$/.test(normalized)
|
|
313
|
+
|| /\/__tests__\/[^/]+\.bats$/.test(normalized);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Find test suites outside `src/` (#642).
|
|
318
|
+
*
|
|
319
|
+
* Bounded to the conventional test directories rather than walking the whole
|
|
320
|
+
* repo: node_modules and build output would otherwise dominate, and a suite that
|
|
321
|
+
* is not under one of these roots is not discoverable by the runner either.
|
|
322
|
+
*/
|
|
323
|
+
function listDirNames(abs: string): string[] {
|
|
324
|
+
try {
|
|
325
|
+
// Explicit encoding: without it readdirSync may hand back Buffer entries.
|
|
326
|
+
// The array check covers stubbed/mocked fs implementations too -- a suite
|
|
327
|
+
// that exercises the "directory missing" path must not crash the scanner.
|
|
328
|
+
const listing = fs.readdirSync(abs, { encoding: 'utf8' });
|
|
329
|
+
return Array.isArray(listing) ? listing : [];
|
|
330
|
+
} catch {
|
|
331
|
+
return [];
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Suites under one shell-test root, as repo-relative forward-slash paths. */
|
|
336
|
+
function suitesInDir(root: string, rel: string): string[] {
|
|
337
|
+
const prefix = rel.split(path.sep).join('/');
|
|
338
|
+
return listDirNames(path.join(root, rel))
|
|
339
|
+
.map(name => `${prefix}/${name}`)
|
|
340
|
+
.filter(isShellSuite);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function discoverShellSuites(cwd?: string): string[] {
|
|
344
|
+
const root = cwd || process.cwd();
|
|
345
|
+
const found = SHELL_SUITE_DIRS.flatMap(rel => suitesInDir(root, rel));
|
|
346
|
+
// Sorted so the report is stable across filesystems with differing readdir order.
|
|
347
|
+
return found.sort();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Files the `.test.ts` pairing convention does not apply to (#642).
|
|
352
|
+
*
|
|
353
|
+
* Shell scripts are exercised by BATS suites, not by a neighbouring module
|
|
354
|
+
* test. Listing them separately keeps the pair rate about the language whose
|
|
355
|
+
* convention it actually describes.
|
|
356
|
+
*/
|
|
357
|
+
function isPairingOutOfScope(filePath: string): boolean {
|
|
358
|
+
return /\.(sh|bash|bats)$/.test(filePath) || !/\.(ts|tsx|js|jsx|mjs|cjs|py|go|java|kt)$/.test(filePath);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** The summary block; shell suites are added to the test total (#642). */
|
|
362
|
+
function buildSummary(counts: LayerCounts, shellOutsideSrc: string[]): TestLayerReport['summary'] {
|
|
363
|
+
return {
|
|
364
|
+
totalSources: counts.sourceFiles.length,
|
|
365
|
+
totalTests: counts.testFiles.length + shellOutsideSrc.length,
|
|
366
|
+
unitTests: counts.unitTests.length,
|
|
367
|
+
integrationTests: counts.integrationTests.length,
|
|
368
|
+
e2eTests: counts.e2eTests.length,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Shell-level suites that the src/ scan did not already pick up (#642).
|
|
374
|
+
*
|
|
375
|
+
* Keeping these out of the count made "Integration: 0" a statement about the
|
|
376
|
+
* scanner rather than about the repository.
|
|
377
|
+
*/
|
|
378
|
+
function shellSuitesOutside(testFiles: string[], dir: string): string[] {
|
|
379
|
+
return discoverShellSuites(dir).filter(f => !testFiles.includes(f));
|
|
254
380
|
}
|
|
255
381
|
|
|
256
382
|
/**
|
|
@@ -264,7 +390,8 @@ export function analyzeTestLayers(cwd?: string): TestLayerReport {
|
|
|
264
390
|
const testFiles = allFiles.filter(f => isTestFile(f));
|
|
265
391
|
const byLayer = (layer: string): string[] => testFiles.filter(f => classifyTestLayer(f) === layer);
|
|
266
392
|
|
|
267
|
-
const { unpairedSources, pairedCount } = computePairing(sourceFiles, allFiles);
|
|
393
|
+
const { unpairedSources, outOfScopeSources, pairedCount } = computePairing(sourceFiles, allFiles);
|
|
394
|
+
const shellOutsideSrc = shellSuitesOutside(testFiles, dir);
|
|
268
395
|
|
|
269
396
|
const counts: LayerCounts = {
|
|
270
397
|
sourceFiles,
|
|
@@ -273,23 +400,25 @@ export function analyzeTestLayers(cwd?: string): TestLayerReport {
|
|
|
273
400
|
integrationTests: byLayer('integration'),
|
|
274
401
|
e2eTests: byLayer('e2e'),
|
|
275
402
|
unpairedSources,
|
|
403
|
+
outOfScopeSources,
|
|
404
|
+
shellSuites: shellOutsideSrc,
|
|
276
405
|
pairedCount,
|
|
277
406
|
};
|
|
278
|
-
const totalNeedingTests = sourceFiles.filter(f => !isExempt(path.basename(f))).length;
|
|
407
|
+
const totalNeedingTests = sourceFiles.filter(f => !isExempt(path.basename(f)) && !isPairingOutOfScope(f)).length;
|
|
279
408
|
|
|
280
409
|
return {
|
|
281
|
-
summary:
|
|
282
|
-
totalSources: sourceFiles.length,
|
|
283
|
-
totalTests: testFiles.length,
|
|
284
|
-
unitTests: counts.unitTests.length,
|
|
285
|
-
integrationTests: counts.integrationTests.length,
|
|
286
|
-
e2eTests: counts.e2eTests.length,
|
|
287
|
-
},
|
|
410
|
+
summary: buildSummary(counts, shellOutsideSrc),
|
|
288
411
|
layer1: {
|
|
289
412
|
pairedSources: pairedCount,
|
|
290
413
|
unpairedSources,
|
|
414
|
+
outOfScopeSources,
|
|
291
415
|
pairRate: totalNeedingTests > 0 ? Math.round((pairedCount / totalNeedingTests) * 100) : 100,
|
|
292
416
|
},
|
|
417
|
+
shellSuites: {
|
|
418
|
+
total: shellOutsideSrc.length,
|
|
419
|
+
bats: shellOutsideSrc.filter(f => f.endsWith('.test.bats') || f.endsWith('.bats')).length,
|
|
420
|
+
files: shellOutsideSrc,
|
|
421
|
+
},
|
|
293
422
|
layer2: {
|
|
294
423
|
integrationFiles: counts.integrationTests.length,
|
|
295
424
|
},
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-653 `check --gates` 单文件自检必须真的跑起来,且 < 2 秒
|
|
3
|
+
* @intent #653 要求提供一条"提交前自检"的快速路径,目标是让模型在写完代码后立刻
|
|
4
|
+
* 自查,而不是等一轮完整 pre-commit(Gate 5 单项就 32.5s)。
|
|
5
|
+
*
|
|
6
|
+
* 实现过程中挖出两个真 bug,它们都属于同一类:**工具没跑起来,却被报成
|
|
7
|
+
* "你的代码有问题"**。
|
|
8
|
+
*
|
|
9
|
+
* 1. `xp-gate check <file>` 把文件路径直接当成 spawnSync 的 `cwd`。cwd 必须是
|
|
10
|
+
* 目录,指向文件时 spawnSync 返回裸 ENOENT,上层打印
|
|
11
|
+
* "Gate 3: failed to run npx tsx" 然后计作 FAIL。
|
|
12
|
+
* 2. Windows 上曾用 `shell: true` 解析 npx.cmd,但 node 把 argv 拼成一整条
|
|
13
|
+
* 未转义的命令行(DEP0190),路径里有反斜杠或空格就会变形;改调 npx.cmd
|
|
14
|
+
* 本身又撞上 CVE-2024-27980 的 EINVAL 加固。
|
|
15
|
+
*
|
|
16
|
+
* 两个修复都要求"文件路径能跑通",而不是"错误信息更好看"。所以断言打在
|
|
17
|
+
* 真实的 passed 计数与退出码上。
|
|
18
|
+
* @covers AC-653-04, AC-653-05
|
|
19
|
+
*/
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
const { execFileSync } = require('node:child_process');
|
|
23
|
+
const path = require('node:path');
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
|
|
26
|
+
const CLI = path.join(__dirname, '..', '..', 'bin', 'xp-gate.js');
|
|
27
|
+
const REPO = path.join(__dirname, '..', '..', '..', '..');
|
|
28
|
+
|
|
29
|
+
function runCheck(relPath) {
|
|
30
|
+
const started = Date.now();
|
|
31
|
+
let stdout = '';
|
|
32
|
+
let status = 0;
|
|
33
|
+
try {
|
|
34
|
+
stdout = execFileSync(process.execPath, [CLI, 'check', relPath, '--gates', 'principles'], {
|
|
35
|
+
cwd: REPO,
|
|
36
|
+
encoding: 'utf8',
|
|
37
|
+
timeout: 120000,
|
|
38
|
+
});
|
|
39
|
+
} catch (err) {
|
|
40
|
+
stdout = `${err.stdout || ''}${err.stderr || ''}`;
|
|
41
|
+
status = err.status || 1;
|
|
42
|
+
}
|
|
43
|
+
return { stdout, status, elapsed: Date.now() - started };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe('check --gates 单文件自检 (#653)', () => {
|
|
47
|
+
it('AC-653-04: 传文件路径时 gate 真的执行,而不是报 spawn 失败', () => {
|
|
48
|
+
const target = 'src/npm-package/lib/doctor-brief.js';
|
|
49
|
+
expect(fs.existsSync(path.join(REPO, target))).toBe(true);
|
|
50
|
+
const { stdout } = runCheck(target);
|
|
51
|
+
// The pre-fix symptom was exactly this string followed by a FAIL verdict.
|
|
52
|
+
expect(stdout).not.toMatch(/failed to run npx tsx/);
|
|
53
|
+
expect(stdout).not.toMatch(/ENOENT|EINVAL/);
|
|
54
|
+
expect(stdout).toMatch(/principles/i);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('AC-653-05: 单文件自检在 2 秒内给出结论', () => {
|
|
58
|
+
const { elapsed } = runCheck('src/npm-package/lib/doctor-brief.js');
|
|
59
|
+
// #653's whole value proposition: self-check before committing beats waiting
|
|
60
|
+
// for a 3-minute pre-commit. A slow "fast path" is not a fast path.
|
|
61
|
+
expect(elapsed).toBeLessThan(2000);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-653 `doctor --brief` 必须一条命令给出全部环境事实
|
|
3
|
+
* @intent 一个会话里 32.7% 的 shell 调用是纯探测(`where gcc`、`Test-Path`、
|
|
4
|
+
* 逐文件 `Get-Content`、版本探测),每次 16s 上下。这类探测的共同点是
|
|
5
|
+
* **答案几乎不变**,却要为每个问题付一次往返。
|
|
6
|
+
*
|
|
7
|
+
* `--brief` 的目标不是"少打印",而是"一次问完":模型拿到一屏结构化事实后
|
|
8
|
+
* 不需要再探任何东西。所以验收标准是**覆盖率**,不是行数——90% 的常见
|
|
9
|
+
* 探测问题必须能在这一屏里找到答案。
|
|
10
|
+
*
|
|
11
|
+
* 一个容易做错的地方:`--brief` 必须是**纯读取**。用户会因为"快"而频繁调用它,
|
|
12
|
+
* 若它顺手修了什么(比如 `--fix` 的行为),频繁调用的代价就从延迟变成了状态。
|
|
13
|
+
* @covers AC-653-01, AC-653-02, AC-653-03
|
|
14
|
+
*/
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const { buildBriefReport, BRIEF_FACTS } = require('../doctor-brief');
|
|
18
|
+
|
|
19
|
+
describe('doctor --brief (#653)', () => {
|
|
20
|
+
it('AC-653-01: 覆盖常见环境探测——语言工具链、hooks、门禁、覆盖率', () => {
|
|
21
|
+
const report = buildBriefReport({ cwd: process.cwd() });
|
|
22
|
+
const keys = Object.keys(report.facts);
|
|
23
|
+
// Each of these replaces a class of exploratory probe that used to cost a
|
|
24
|
+
// round trip. If one is missing, models go back to probing for it manually.
|
|
25
|
+
for (const required of ['hooks', 'language', 'adapters', 'coverage', 'gateState']) {
|
|
26
|
+
expect(keys).toContain(required);
|
|
27
|
+
}
|
|
28
|
+
expect(BRIEF_FACTS.length).toBeGreaterThanOrEqual(5);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('AC-653-02: 输出必须是可直接消费的结构,不是需要再解析的散文', () => {
|
|
32
|
+
const report = buildBriefReport({ cwd: process.cwd() });
|
|
33
|
+
// A model that has to regex a sentence has not saved a round trip.
|
|
34
|
+
expect(typeof report.facts.hooks.effectivePath).toBe('string');
|
|
35
|
+
expect(Array.isArray(report.facts.language.detected)).toBe(true);
|
|
36
|
+
expect(typeof report.facts.coverage.found).toBe('boolean');
|
|
37
|
+
// One-line summary for humans, but the structure carries the facts.
|
|
38
|
+
expect(typeof report.summary).toBe('string');
|
|
39
|
+
expect(report.summary.split('\n').length).toBeLessThanOrEqual(3);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('AC-653-03: --brief 纯读取,不修改任何状态', () => {
|
|
43
|
+
const { execFileSync } = require('node:child_process');
|
|
44
|
+
const fs = require('node:fs');
|
|
45
|
+
const path = require('node:path');
|
|
46
|
+
const before = fs.readdirSync(process.cwd()).sort().join('\n');
|
|
47
|
+
try {
|
|
48
|
+
execFileSync(process.execPath, [
|
|
49
|
+
path.join(__dirname, '..', '..', 'bin', 'xp-gate.js'), 'doctor', '--brief', '--json',
|
|
50
|
+
], { cwd: process.cwd(), encoding: 'utf8', timeout: 120000 });
|
|
51
|
+
} catch {
|
|
52
|
+
// Exit code is not the assertion here; mutation is.
|
|
53
|
+
}
|
|
54
|
+
const after = fs.readdirSync(process.cwd()).sort().join('\n');
|
|
55
|
+
// The real cost of a --fix sneaking in is not one bad run: users call --brief
|
|
56
|
+
// often BECAUSE it is cheap, so an unintended write compounds every session.
|
|
57
|
+
expect(after).toBe(before);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `xp-gate doctor --brief` — one command that answers every common environment
|
|
3
|
+
* probe (#653).
|
|
4
|
+
*
|
|
5
|
+
* Why this exists: 32.7% of shell calls in a working session were pure probing
|
|
6
|
+
* (`where gcc`, `Test-Path`, per-file `Get-Content`, version checks), at ~16s
|
|
7
|
+
* each. The common thread is that the answers barely change, yet every question
|
|
8
|
+
* costs a full round trip. The fix is not to print less — it is to ask once.
|
|
9
|
+
*
|
|
10
|
+
* Two rules this module holds itself to:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Read-only.** `--brief` is meant to be called often precisely because it is
|
|
13
|
+
* cheap. Anything that writes would make frequent calling compound state
|
|
14
|
+
* changes instead of only latency.
|
|
15
|
+
* 2. **Structured first, summary second.** A model that has to regex a sentence
|
|
16
|
+
* has not saved a round trip. `facts` carries the data; `summary` is a
|
|
17
|
+
* convenience for humans and nothing depends on it.
|
|
18
|
+
*
|
|
19
|
+
* No runtime dependencies: this ships inside the zero-install package.
|
|
20
|
+
*/
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
const fs = require('node:fs');
|
|
24
|
+
const path = require('node:path');
|
|
25
|
+
const { execFileSync } = require('node:child_process');
|
|
26
|
+
|
|
27
|
+
/** The fact groups `--brief` promises. Tests pin this list; adding a group here
|
|
28
|
+
* without teaching the model to use it only adds output, so keep it deliberate. */
|
|
29
|
+
const BRIEF_FACTS = ['hooks', 'language', 'adapters', 'coverage', 'gateState'];
|
|
30
|
+
|
|
31
|
+
/** Run a command, returning trimmed stdout or null. Never throws. */
|
|
32
|
+
function tryExec(cmd, args, cwd) {
|
|
33
|
+
try {
|
|
34
|
+
const out = execFileSync(cmd, args, {
|
|
35
|
+
cwd,
|
|
36
|
+
encoding: 'utf8',
|
|
37
|
+
timeout: 5000,
|
|
38
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
39
|
+
});
|
|
40
|
+
return out.trim();
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Which hooks actually execute, and where they live. */
|
|
47
|
+
function readHooks(cwd) {
|
|
48
|
+
const hooksPath = tryExec('git', ['config', '--get', 'core.hooksPath'], cwd);
|
|
49
|
+
const effective = hooksPath || '.git/hooks';
|
|
50
|
+
const dir = path.isAbsolute(effective) ? effective : path.join(cwd, effective);
|
|
51
|
+
let installed = [];
|
|
52
|
+
try {
|
|
53
|
+
installed = fs.readdirSync(dir).filter((name) => !name.endsWith('.sample'));
|
|
54
|
+
} catch {
|
|
55
|
+
installed = [];
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
hooksPath: hooksPath || null,
|
|
59
|
+
effectivePath: dir,
|
|
60
|
+
installed,
|
|
61
|
+
// #451: when core.hooksPath is set, the repo copy is NOT what runs.
|
|
62
|
+
globalOverride: Boolean(hooksPath),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Project language markers, cheapest signal first. */
|
|
67
|
+
const LANGUAGE_MARKERS = [
|
|
68
|
+
['typescript', ['tsconfig.json']],
|
|
69
|
+
['python', ['pyproject.toml', 'setup.py', 'requirements.txt']],
|
|
70
|
+
['go', ['go.mod']],
|
|
71
|
+
['java', ['pom.xml', 'build.gradle', 'build.gradle.kts']],
|
|
72
|
+
['kotlin', ['build.gradle.kts']],
|
|
73
|
+
['dart', ['pubspec.yaml']],
|
|
74
|
+
['rust', ['Cargo.toml']],
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
function detectLanguages(cwd) {
|
|
78
|
+
const found = [];
|
|
79
|
+
for (const [lang, markers] of LANGUAGE_MARKERS) {
|
|
80
|
+
if (markers.some((m) => fs.existsSync(path.join(cwd, m)))) found.push(lang);
|
|
81
|
+
}
|
|
82
|
+
return { detected: found, primary: found[0] || null };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Which adapters are reachable via the documented 3-tier resolution. */
|
|
86
|
+
function detectAdapters(cwd) {
|
|
87
|
+
const tiers = [
|
|
88
|
+
path.join(process.env.HOME || process.env.USERPROFILE || '', '.config', 'xp-gate', 'adapters'),
|
|
89
|
+
path.join(cwd, 'githooks', 'adapters'),
|
|
90
|
+
];
|
|
91
|
+
const seen = new Set();
|
|
92
|
+
const sources = [];
|
|
93
|
+
for (const dir of tiers) {
|
|
94
|
+
let names = [];
|
|
95
|
+
try {
|
|
96
|
+
names = fs.readdirSync(dir).filter((n) => n.endsWith('.sh'));
|
|
97
|
+
} catch {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
for (const n of names) seen.add(n);
|
|
101
|
+
sources.push({ dir, count: names.length });
|
|
102
|
+
}
|
|
103
|
+
return { count: seen.size, sources };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Where coverage would come from, if anywhere. */
|
|
107
|
+
const COVERAGE_CANDIDATES = [
|
|
108
|
+
'coverage/coverage-final.json',
|
|
109
|
+
'coverage/lcov.info',
|
|
110
|
+
'target/site/jacoco/jacoco.xml',
|
|
111
|
+
'build/reports/jacoco/test/jacocoTestReport.xml',
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
function detectCoverage(cwd) {
|
|
115
|
+
const hits = COVERAGE_CANDIDATES.filter((rel) => fs.existsSync(path.join(cwd, rel)));
|
|
116
|
+
return { found: hits.length > 0, files: hits };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Last gate artifact, so callers stop re-reading `.xp-gate/` by hand. */
|
|
120
|
+
function readGateState(cwd) {
|
|
121
|
+
const artifact = path.join(cwd, '.xp-gate', 'quality-status', 'main.json');
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(fs.readFileSync(artifact, 'utf8'));
|
|
124
|
+
return {
|
|
125
|
+
artifact,
|
|
126
|
+
commit: parsed.commit || null,
|
|
127
|
+
gates: Object.keys(parsed).filter((k) => k.startsWith('gate')).length,
|
|
128
|
+
};
|
|
129
|
+
} catch {
|
|
130
|
+
// No artifact is a fact worth stating, not an error: "never run" and
|
|
131
|
+
// "failed to read" would otherwise be indistinguishable.
|
|
132
|
+
return { artifact, commit: null, gates: 0 };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Build the brief report.
|
|
138
|
+
*
|
|
139
|
+
* @param {{cwd?: string}} opts
|
|
140
|
+
* @returns {{facts: object, summary: string}}
|
|
141
|
+
*/
|
|
142
|
+
function buildBriefReport(opts = {}) {
|
|
143
|
+
const cwd = opts.cwd || process.cwd();
|
|
144
|
+
const facts = {
|
|
145
|
+
hooks: readHooks(cwd),
|
|
146
|
+
language: detectLanguages(cwd),
|
|
147
|
+
adapters: detectAdapters(cwd),
|
|
148
|
+
coverage: detectCoverage(cwd),
|
|
149
|
+
gateState: readGateState(cwd),
|
|
150
|
+
};
|
|
151
|
+
const summary = [
|
|
152
|
+
`lang=${facts.language.primary || 'unknown'} adapters=${facts.adapters.count} ` +
|
|
153
|
+
`coverage=${facts.coverage.found ? 'yes' : 'no'} hooks=${facts.hooks.globalOverride ? 'global' : 'local'}`,
|
|
154
|
+
`hooksPath=${facts.hooks.effectivePath}`,
|
|
155
|
+
].join('\n');
|
|
156
|
+
return { facts, summary };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = { BRIEF_FACTS, buildBriefReport };
|
package/lib/doctor.js
CHANGED
|
@@ -2873,7 +2873,7 @@ function diagnoseLanguageTools() {
|
|
|
2873
2873
|
// #488: an unrecognised flag used to be silently ignored — the npm release
|
|
2874
2874
|
// that predates --sync-hooks made "doctor --sync-hooks" a silent no-op and the
|
|
2875
2875
|
// user believed a sync had happened. Fail loudly instead.
|
|
2876
|
-
const KNOWN_FLAGS = ['--fix', '--sync-hooks', '--force', '--install-tools', '--json', '--format', '--refresh-env'];
|
|
2876
|
+
const KNOWN_FLAGS = ['--fix', '--sync-hooks', '--force', '--install-tools', '--json', '--format', '--refresh-env', '--brief'];
|
|
2877
2877
|
|
|
2878
2878
|
/**
|
|
2879
2879
|
* The accepted flag surface of `doctor`, as a pure function.
|
|
@@ -2931,11 +2931,31 @@ function doctorFlagContract(args) {
|
|
|
2931
2931
|
errors.push('--install-tools is only read by --fix, and it is the only flag that installs toolchains onto this machine');
|
|
2932
2932
|
errors.push('Pass --fix --install-tools, or drop --install-tools');
|
|
2933
2933
|
}
|
|
2934
|
-
|
|
2934
|
+
// #653: --brief exists to collapse a class of exploratory probes (`where gcc`,
|
|
2935
|
+
// Test-Path, per-file reads) into one cheap read. It is read-only by contract:
|
|
2936
|
+
// users call it often precisely because it is cheap, so a write smuggled in
|
|
2937
|
+
// would compound state changes instead of only latency.
|
|
2938
|
+
const briefFlag = args.includes('--brief');
|
|
2939
|
+
if (briefFlag && (fixMode || syncHooks || installToolsFlag || force)) {
|
|
2940
|
+
errors.push('--brief is a read-only fact dump and cannot be combined with --fix, --sync-hooks, --install-tools or --force');
|
|
2941
|
+
}
|
|
2942
|
+
return { errors, fixMode, syncHooks, force, installToolsFlag, jsonMode, refreshEnv, briefFlag };
|
|
2935
2943
|
}
|
|
2936
2944
|
|
|
2937
2945
|
async function doctor(args) {
|
|
2938
|
-
const { errors, fixMode, syncHooks, force, installToolsFlag, jsonMode, refreshEnv } = doctorFlagContract(args);
|
|
2946
|
+
const { errors, fixMode, syncHooks, force, installToolsFlag, jsonMode, refreshEnv, briefFlag } = doctorFlagContract(args);
|
|
2947
|
+
if (briefFlag && errors.length === 0) {
|
|
2948
|
+
// Answers the whole "what is this machine set up like" question in one shot.
|
|
2949
|
+
const { buildBriefReport } = require('./doctor-brief');
|
|
2950
|
+
const report = buildBriefReport({ cwd: process.cwd() });
|
|
2951
|
+
if (jsonMode) {
|
|
2952
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
2953
|
+
} else {
|
|
2954
|
+
process.stdout.write(`${report.summary}\n`);
|
|
2955
|
+
}
|
|
2956
|
+
process.exitCode = 0;
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2939
2959
|
if (errors.length > 0) {
|
|
2940
2960
|
for (const error of errors) console.error(error);
|
|
2941
2961
|
return 1;
|
package/lib/gate-runner.js
CHANGED
|
@@ -320,8 +320,49 @@ const GATE_NOT_RUN = 'NOT_RUN';
|
|
|
320
320
|
* Finds the .ts entry point, spawns tsx with --run flag.
|
|
321
321
|
* Falls back to bash script if TypeScript module not found.
|
|
322
322
|
*/
|
|
323
|
+
/**
|
|
324
|
+
* Resolve the working directory for a gate run.
|
|
325
|
+
*
|
|
326
|
+
* `targetPath` is documented as "file or directory" (that is what
|
|
327
|
+
* `xp-gate check <file>` passes), but spawnSync's `cwd` must be a directory.
|
|
328
|
+
* Resolving a FILE path into cwd made spawnSync fail with a bare ENOENT that read
|
|
329
|
+
* as "Gate 3 failed to run npx tsx" — a tool failure reported under the label of a
|
|
330
|
+
* code verdict. The file's own directory is the meaningful cwd in that case.
|
|
331
|
+
*/
|
|
332
|
+
function resolveGateCwd(targetPath) {
|
|
333
|
+
if (!targetPath) return process.cwd();
|
|
334
|
+
const resolved = path.resolve(targetPath);
|
|
335
|
+
try {
|
|
336
|
+
if (fs.statSync(resolved).isFile()) return path.dirname(resolved);
|
|
337
|
+
} catch {
|
|
338
|
+
// Nonexistent path: leave it for the gate itself to report.
|
|
339
|
+
}
|
|
340
|
+
return resolved;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Resolve how to invoke npx without a shell.
|
|
345
|
+
*
|
|
346
|
+
* `shell: true` on Windows looked like the right fix for resolving npx.cmd, but
|
|
347
|
+
* node passes the argv to CreateProcess as one concatenated string without
|
|
348
|
+
* escaping (DEP0190) — so any entry path with a backslash or space produced a
|
|
349
|
+
* malformed command line and spawnSync failed with a bare ENOENT. The gate then
|
|
350
|
+
* reported FAIL while nothing had actually run: a tool failure wearing the verdict
|
|
351
|
+
* "your code is broken".
|
|
352
|
+
*
|
|
353
|
+
* Calling npx.cmd directly is not the fix either: since the CVE-2024-27980
|
|
354
|
+
* hardening, node refuses .cmd/.bat from spawnSync without a shell and returns
|
|
355
|
+
* EINVAL. Running npx's own cli.js with the current node binary sidesteps both —
|
|
356
|
+
* plain argv, no shell, no batch wrapper.
|
|
357
|
+
*/
|
|
358
|
+
function resolveNpxInvocation(args) {
|
|
359
|
+
const npxCli = path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npx-cli.js');
|
|
360
|
+
if (fs.existsSync(npxCli)) return [process.execPath, [npxCli, ...args]];
|
|
361
|
+
return ['npx', args];
|
|
362
|
+
}
|
|
363
|
+
|
|
323
364
|
function runTsGate(gateModule, targetPath, opts = {}) {
|
|
324
|
-
const targetCwd =
|
|
365
|
+
const targetCwd = resolveGateCwd(targetPath);
|
|
325
366
|
const entry = findTsGateEntry(gateModule);
|
|
326
367
|
if (!entry) {
|
|
327
368
|
// Fallback to bash script
|
|
@@ -344,10 +385,15 @@ function runTsGate(gateModule, targetPath, opts = {}) {
|
|
|
344
385
|
// malformed argv, and reports a killed child (timeout, signal) as a null status.
|
|
345
386
|
// All three mean "the gate did not report a verdict", which must not be read as
|
|
346
387
|
// success: a gate that never ran is not a gate that passed.
|
|
388
|
+
//
|
|
389
|
+
// spawnSync reports a spawn failure (npx missing) on `error`, throws only on a
|
|
390
|
+
// malformed argv, and reports a killed child (timeout, signal) as a null status.
|
|
391
|
+
// All three mean "the gate did not report a verdict", which must not be read as
|
|
392
|
+
// success: a gate that never ran is not a gate that passed.
|
|
393
|
+
const [command, commandArgs] = resolveNpxInvocation(args);
|
|
347
394
|
try {
|
|
348
|
-
const result = spawnSync(
|
|
395
|
+
const result = spawnSync(command, commandArgs, {
|
|
349
396
|
stdio: 'inherit',
|
|
350
|
-
shell: process.platform === 'win32',
|
|
351
397
|
cwd: targetCwd,
|
|
352
398
|
timeout: TS_GATE_TIMEOUT_MS,
|
|
353
399
|
});
|
package/mock-policy/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MOCK-POLICY KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.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.35.
|
|
3
|
+
"version": "0.35.6",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
|
@@ -19,6 +19,5 @@ export interface ReviewOutcome {
|
|
|
19
19
|
* plus whatever the caller says was addressed, then re-votes.
|
|
20
20
|
*/
|
|
21
21
|
export declare function runReview(opts: DelphiOptions, subject: string, addressed?: string, signal?: AbortSignal): Promise<ReviewOutcome>;
|
|
22
|
-
/** Human-readable transcript for the tool result. */
|
|
23
22
|
export declare function formatOutcome(outcome: ReviewOutcome): string;
|
|
24
23
|
//# sourceMappingURL=delphi-run.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delphi-run.d.ts","sourceRoot":"","sources":["../src/delphi-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"delphi-run.d.ts","sourceRoot":"","sources":["../src/delphi-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,aAAa,EAA6B,WAAW,EAAE,MAAM,aAAa,CAAA;AAoBxF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAA;IACvC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AA0CD;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,MAAM,EACf,SAAS,SAAK,EACd,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CA0CxB;AAoDD,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAe5D"}
|
|
@@ -94,15 +94,65 @@ export async function runReview(opts, subject, addressed = "", signal) {
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
/** Human-readable transcript for the tool result. */
|
|
97
|
+
/**
|
|
98
|
+
* Reduce a seat's raw report to the part worth reading (#670).
|
|
99
|
+
*
|
|
100
|
+
* The seat was asked for `### Findings` / `### VERDICT` / `### CONFIDENCE`; the
|
|
101
|
+
* verdict and confidence are already on the tally line above, so repeating them
|
|
102
|
+
* here only makes the block longer. What the caller cannot get anywhere else is
|
|
103
|
+
* the Findings table, which is why the report is trimmed down to it.
|
|
104
|
+
*/
|
|
105
|
+
function findingsExcerpt(report) {
|
|
106
|
+
const text = report.trim();
|
|
107
|
+
if (text === "")
|
|
108
|
+
return "";
|
|
109
|
+
// Split on the section headings the prompt asks for; no regex escaping needed,
|
|
110
|
+
// which keeps this readable and avoids a silently-mangled character class.
|
|
111
|
+
const lines = text.split(/\r?\n/);
|
|
112
|
+
const start = lines.findIndex((line) => /^#{1,3}\s*Findings\s*$/i.test(line));
|
|
113
|
+
if (start === -1) {
|
|
114
|
+
// No Findings heading: keep everything before VERDICT rather than returning
|
|
115
|
+
// nothing on an unexpected format.
|
|
116
|
+
const stop = lines.findIndex((line) => /^#{1,3}\s*VERDICT/i.test(line));
|
|
117
|
+
return (stop === -1 ? lines : lines.slice(0, stop)).join("\n").trim();
|
|
118
|
+
}
|
|
119
|
+
let end = lines.length;
|
|
120
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
121
|
+
if (/^#{1,3}\s*[A-Z]/i.test(lines[i])) {
|
|
122
|
+
end = i;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return lines.slice(start + 1, end).join("\n").trim();
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* One seat's tally line, plus its findings when this is the final round (#670).
|
|
130
|
+
*
|
|
131
|
+
* Only the final round expands: printing every round's full report would bury the
|
|
132
|
+
* conclusion under five historical copies, and what the caller needs is "what is
|
|
133
|
+
* still wrong", not a transcript.
|
|
134
|
+
*/
|
|
135
|
+
function verdictLines(v, expand) {
|
|
136
|
+
const model = v.resolvedModel ? `${v.requestedModel} -> ${v.resolvedModel}` : v.requestedModel;
|
|
137
|
+
const note = v.error ? ` (${v.error})` : "";
|
|
138
|
+
const lines = [`- [${v.role}] ${v.id} (${model}): ${v.verdict} confidence=${v.confidence}${note}`];
|
|
139
|
+
if (!expand)
|
|
140
|
+
return lines;
|
|
141
|
+
const excerpt = findingsExcerpt(v.report);
|
|
142
|
+
if (excerpt === "")
|
|
143
|
+
return lines;
|
|
144
|
+
for (const line of excerpt.split(/\r?\n/))
|
|
145
|
+
lines.push(` ${line}`);
|
|
146
|
+
return lines;
|
|
147
|
+
}
|
|
97
148
|
export function formatOutcome(outcome) {
|
|
98
149
|
const lines = [];
|
|
150
|
+
const lastRound = outcome.rounds.length;
|
|
99
151
|
for (const r of outcome.rounds) {
|
|
100
152
|
lines.push(`## Round ${r.round} — approvals ${r.approvals}/${r.validCount} valid`);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
lines.push(`- [${v.role}] ${v.id} (${model}): ${v.verdict} confidence=${v.confidence}${note}`);
|
|
105
|
-
}
|
|
153
|
+
const expand = r.round === lastRound;
|
|
154
|
+
for (const v of r.verdicts)
|
|
155
|
+
lines.push(...verdictLines(v, expand));
|
|
106
156
|
lines.push("");
|
|
107
157
|
}
|
|
108
158
|
lines.push(outcome.consensus
|
package/plugins/dsh/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boyingliu01/dsh-plugin-xp-gate",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.6",
|
|
4
4
|
"description": "XP-Gate deterministic quality gates + AI workflow skills as a DeepSeek Harness (DSH) native plugin: gate-check / gate-principles / gate-arch tools + 12 SKILL.md skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import { formatOutcome } from "./delphi-run.js"
|
|
3
|
+
import type { ReviewOutcome } from "./delphi-run.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @test REQ-670 delphi 报告必须回传专家意见正文
|
|
7
|
+
* @intent `formatOutcome` 只回传 verdict 与 confidence,把专家写的
|
|
8
|
+
* `### Findings` 表格整块丢掉。调用方拿到的是一串 REQUEST_CHANGES
|
|
9
|
+
* 加上 0 条具体问题——评审消耗了真实模型调用,产出的却是不可行动的信息。
|
|
10
|
+
*
|
|
11
|
+
* 这个 bug 有个特殊之处:**数据一直都在**。`ExpertVerdict.report` 完整
|
|
12
|
+
* 保留了专家原文,只是呈现层从来没读过它。所以这不是"信息不足",是
|
|
13
|
+
* "攒了信息然后扔掉"——比前者更难发现,因为所有中间层看起来都是对的。
|
|
14
|
+
*
|
|
15
|
+
* 修复必须小心一个副作用:报告是多轮匿名评审,专家在后续轮次会看到
|
|
16
|
+
* 聚合票型。若把每轮全文都打印出来,输出会迅速膨胀到不可读(3 席 × 5 轮
|
|
17
|
+
* × 300 字)。所以正文只在**最后一轮**展开,前面各轮仍只给票型——读者
|
|
18
|
+
* 需要的是"最终为什么不通过",不是五份历史副本。
|
|
19
|
+
* @covers AC-670-66, AC-670-67
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const FINDINGS_REPORT =
|
|
23
|
+
"### Findings\n| ID | Severity | Description | Location | Fix |\n|---|---|---|---|---|\n" +
|
|
24
|
+
"| F1 | critical | exit code discarded | run.ts:42 | propagate it |\n" +
|
|
25
|
+
"\n### VERDICT\nREQUEST_CHANGES\n### CONFIDENCE\n9"
|
|
26
|
+
|
|
27
|
+
const APPROVED_REPORT = "### Findings\nNONE\n\n### VERDICT\nAPPROVED\n### CONFIDENCE\n8"
|
|
28
|
+
|
|
29
|
+
function verdict(id: string, role: string, report: string, verdictValue: string, confidence: number) {
|
|
30
|
+
return {
|
|
31
|
+
id,
|
|
32
|
+
role,
|
|
33
|
+
requestedModel: `m-${id}`,
|
|
34
|
+
resolvedModel: `res-${id}`,
|
|
35
|
+
verdict: verdictValue as "APPROVED" | "REQUEST_CHANGES",
|
|
36
|
+
confidence,
|
|
37
|
+
counted: true,
|
|
38
|
+
report,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function outcomeOf(rounds: Array<{ round: number; verdicts: ReturnType<typeof verdict>[] }>): ReviewOutcome {
|
|
43
|
+
const mapped = rounds.map((r) => ({
|
|
44
|
+
round: r.round,
|
|
45
|
+
approvals: r.verdicts.filter((v) => v.verdict === "APPROVED").length,
|
|
46
|
+
validCount: r.verdicts.length,
|
|
47
|
+
verdicts: r.verdicts,
|
|
48
|
+
}))
|
|
49
|
+
return {
|
|
50
|
+
rounds: mapped,
|
|
51
|
+
consensus: false,
|
|
52
|
+
finalRatio: 0,
|
|
53
|
+
totalRounds: mapped.length,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe("formatOutcome 回传专家意见 (#670)", () => {
|
|
58
|
+
it("AC-670-66: 最终轮的专家意见正文必须出现在输出里", () => {
|
|
59
|
+
const out = outcomeOf([
|
|
60
|
+
{
|
|
61
|
+
round: 1,
|
|
62
|
+
verdicts: [
|
|
63
|
+
verdict("A", "architecture", FINDINGS_REPORT, "REQUEST_CHANGES", 9),
|
|
64
|
+
verdict("B", "technical", APPROVED_REPORT, "APPROVED", 8),
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
])
|
|
68
|
+
const text = formatOutcome(out)
|
|
69
|
+
// The whole point: a reviewer must be able to read what was wrong.
|
|
70
|
+
expect(text).toContain("exit code discarded")
|
|
71
|
+
expect(text).toContain("propagate it")
|
|
72
|
+
// The verdict/confidence are already on the tally line; the excerpt must not
|
|
73
|
+
// repeat them, or every seat's block doubles in size.
|
|
74
|
+
expect(text).not.toContain("### VERDICT")
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it("AC-670-67: 中间轮次只给票型,不重复整段正文(否则输出不可读)", () => {
|
|
78
|
+
const out = outcomeOf([
|
|
79
|
+
{
|
|
80
|
+
round: 1,
|
|
81
|
+
verdicts: [verdict("A", "architecture", FINDINGS_REPORT, "REQUEST_CHANGES", 9)],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
round: 2,
|
|
85
|
+
verdicts: [verdict("A", "architecture", APPROVED_REPORT, "APPROVED", 8)],
|
|
86
|
+
},
|
|
87
|
+
])
|
|
88
|
+
const text = formatOutcome(out)
|
|
89
|
+
// The final round approved with NONE, so the earlier rejection's findings are
|
|
90
|
+
// historical and must not be expanded -- otherwise the reader sees a problem
|
|
91
|
+
// that was already resolved as if it were still open.
|
|
92
|
+
const occurrences = text.split("exit code discarded").length - 1
|
|
93
|
+
expect(occurrences).toBe(0)
|
|
94
|
+
// But the earlier round's tally line is still there: history is not erased.
|
|
95
|
+
expect(text).toContain("## Round 1")
|
|
96
|
+
expect(text).toContain("REQUEST_CHANGES")
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it("AC-670-66: 报告无 Findings 标题时仍保留正文,而不是整块丢弃", () => {
|
|
100
|
+
// The original bug's shape was "unrecognized format -> print nothing". A
|
|
101
|
+
// fallback that also prints nothing would reproduce it under a new input.
|
|
102
|
+
const freeform = "The suffix resets $? so every gate reads as passing.\n\n### VERDICT\nREQUEST_CHANGES\n### CONFIDENCE\n9"
|
|
103
|
+
const out = outcomeOf([
|
|
104
|
+
{ round: 1, verdicts: [verdict("A", "architecture", freeform, "REQUEST_CHANGES", 9)] },
|
|
105
|
+
])
|
|
106
|
+
const text = formatOutcome(out)
|
|
107
|
+
expect(text).toContain("resets $?")
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { TOOL_ABORTED } from "@deepseek-ai/dsh-tools"
|
|
12
12
|
import { HarnessError } from "@deepseek-ai/dsh-llm"
|
|
13
13
|
|
|
14
|
-
import type { DelphiOptions, ExpertSeat, RoundResult } from "./delphi.js"
|
|
14
|
+
import type { DelphiOptions, ExpertSeat, ExpertVerdict, RoundResult } from "./delphi.js"
|
|
15
15
|
import { runRound } from "./delphi.js"
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -132,15 +132,62 @@ export async function runReview(
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/** Human-readable transcript for the tool result. */
|
|
135
|
+
/**
|
|
136
|
+
* Reduce a seat's raw report to the part worth reading (#670).
|
|
137
|
+
*
|
|
138
|
+
* The seat was asked for `### Findings` / `### VERDICT` / `### CONFIDENCE`; the
|
|
139
|
+
* verdict and confidence are already on the tally line above, so repeating them
|
|
140
|
+
* here only makes the block longer. What the caller cannot get anywhere else is
|
|
141
|
+
* the Findings table, which is why the report is trimmed down to it.
|
|
142
|
+
*/
|
|
143
|
+
function findingsExcerpt(report: string): string {
|
|
144
|
+
const text = report.trim()
|
|
145
|
+
if (text === "") return ""
|
|
146
|
+
// Split on the section headings the prompt asks for; no regex escaping needed,
|
|
147
|
+
// which keeps this readable and avoids a silently-mangled character class.
|
|
148
|
+
const lines = text.split(/\r?\n/)
|
|
149
|
+
const start = lines.findIndex((line) => /^#{1,3}\s*Findings\s*$/i.test(line))
|
|
150
|
+
if (start === -1) {
|
|
151
|
+
// No Findings heading: keep everything before VERDICT rather than returning
|
|
152
|
+
// nothing on an unexpected format.
|
|
153
|
+
const stop = lines.findIndex((line) => /^#{1,3}\s*VERDICT/i.test(line))
|
|
154
|
+
return (stop === -1 ? lines : lines.slice(0, stop)).join("\n").trim()
|
|
155
|
+
}
|
|
156
|
+
let end = lines.length
|
|
157
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
158
|
+
if (/^#{1,3}\s*[A-Z]/i.test(lines[i])) {
|
|
159
|
+
end = i
|
|
160
|
+
break
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return lines.slice(start + 1, end).join("\n").trim()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* One seat's tally line, plus its findings when this is the final round (#670).
|
|
168
|
+
*
|
|
169
|
+
* Only the final round expands: printing every round's full report would bury the
|
|
170
|
+
* conclusion under five historical copies, and what the caller needs is "what is
|
|
171
|
+
* still wrong", not a transcript.
|
|
172
|
+
*/
|
|
173
|
+
function verdictLines(v: ExpertVerdict, expand: boolean): string[] {
|
|
174
|
+
const model = v.resolvedModel ? `${v.requestedModel} -> ${v.resolvedModel}` : v.requestedModel
|
|
175
|
+
const note = v.error ? ` (${v.error})` : ""
|
|
176
|
+
const lines = [`- [${v.role}] ${v.id} (${model}): ${v.verdict} confidence=${v.confidence}${note}`]
|
|
177
|
+
if (!expand) return lines
|
|
178
|
+
const excerpt = findingsExcerpt(v.report)
|
|
179
|
+
if (excerpt === "") return lines
|
|
180
|
+
for (const line of excerpt.split(/\r?\n/)) lines.push(` ${line}`)
|
|
181
|
+
return lines
|
|
182
|
+
}
|
|
183
|
+
|
|
135
184
|
export function formatOutcome(outcome: ReviewOutcome): string {
|
|
136
185
|
const lines: string[] = []
|
|
186
|
+
const lastRound = outcome.rounds.length
|
|
137
187
|
for (const r of outcome.rounds) {
|
|
138
188
|
lines.push(`## Round ${r.round} — approvals ${r.approvals}/${r.validCount} valid`)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const note = v.error ? ` (${v.error})` : ""
|
|
142
|
-
lines.push(`- [${v.role}] ${v.id} (${model}): ${v.verdict} confidence=${v.confidence}${note}`)
|
|
143
|
-
}
|
|
189
|
+
const expand = r.round === lastRound
|
|
190
|
+
for (const v of r.verdicts) lines.push(...verdictLines(v, expand))
|
|
144
191
|
lines.push("")
|
|
145
192
|
}
|
|
146
193
|
lines.push(
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.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.35.
|
|
3
|
+
"version": "0.35.6",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Qoder. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 35df3831
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.6.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|