@boyingliu01/xp-gate 0.35.8 → 0.35.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/__tests__/coverage-threshold.test.js +80 -0
- package/lib/__tests__/debt-classify.test.js +78 -0
- package/lib/coverage-threshold.js +88 -0
- package/lib/debt-classify.js +86 -0
- package/lib/gate-coverage.js +155 -9
- 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/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/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/sprint-flow/references/phase-3-build.md +14 -1
- 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
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-605 覆盖率阈值必须只有一个真相源,且"未声明"与"声明为默认"可区分
|
|
3
|
+
* @intent #605 指出阈值散落在 4+ 处。实测这里的问题比"散落"严重:同一个覆盖率
|
|
4
|
+
* 概念有**两条独立的闸门,读同样的数字,却给出不同的门槛**。
|
|
5
|
+
*
|
|
6
|
+
* githooks/pre-commit ${COVERAGE_THRESHOLD:-80} —— 平面 80%
|
|
7
|
+
* vitest.config.ts lines 60 / statements 60
|
|
8
|
+
* functions 81 / branches 79
|
|
9
|
+
*
|
|
10
|
+
* 而 vitest 侧那条的注释自己写着"这些数字是实测下限,不是 80% 目标"。
|
|
11
|
+
* 也就是说两条闸门对"这个仓库的覆盖率该是多少"给出了互相矛盾的答案,
|
|
12
|
+
* 而谁都没错——它们各自是本地合理决定的总和。
|
|
13
|
+
*
|
|
14
|
+
* **"散落"本身不是问题,"同一问题有多个互相矛盾的答案"才是。** 所以这里
|
|
15
|
+
* 不追求"把所有阈值塞进一个文件",而是保证:给定一个阈值概念,解析结果的
|
|
16
|
+
* (值, 来源) 是唯一确定且可解释的。
|
|
17
|
+
*
|
|
18
|
+
* 第二条要求是 #561 的教训:``COVERAGE_THRESHOLD:-80`` 让"没声明"和"声明
|
|
19
|
+
* 为 80"在行为上完全相同,于是文档说 80%、实际生效 60% 时没有任何东西会报警。
|
|
20
|
+
* 来源必须能让调用方区分这两种情况,否则"有效画像"还是一句空话。
|
|
21
|
+
* @covers AC-605-01, AC-605-02, AC-605-03
|
|
22
|
+
*/
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const { resolveCoverageThreshold, describeThreshold } = require('../coverage-threshold');
|
|
26
|
+
|
|
27
|
+
describe('覆盖率阈值单一真相源 (#605)', () => {
|
|
28
|
+
it('AC-605-01: 未声明时标注为 implicit,并给出实际生效值', () => {
|
|
29
|
+
const r = resolveCoverageThreshold({});
|
|
30
|
+
expect(r.value).toBe(80);
|
|
31
|
+
expect(r.source).toBe('implicit');
|
|
32
|
+
expect(r.declared).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('AC-605-02: 显式声明为同一个值时必须能与未声明区分', () => {
|
|
36
|
+
// The #561 failure shape: `:-80` made "nobody said" and "someone said 80"
|
|
37
|
+
// identical, so a doc claiming 80% while the live config said 60% raised no
|
|
38
|
+
// alarm anywhere. Same number, different meaning, must stay distinguishable.
|
|
39
|
+
const impl = resolveCoverageThreshold({});
|
|
40
|
+
const expl = resolveCoverageThreshold({ vitest: { lines: 80 } });
|
|
41
|
+
expect(impl.value).toBe(expl.value);
|
|
42
|
+
expect(impl.source).not.toBe(expl.source);
|
|
43
|
+
expect(expl.declared).toBe(true);
|
|
44
|
+
expect(expl.source).toBe('vitest.config.ts');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('AC-605-03: 两条闸门矛盾时必须报告冲突,而不是静默取一个', () => {
|
|
48
|
+
// Measured state: vitest declares lines 60 while pre-commit defaults to 80.
|
|
49
|
+
// Picking either silently is how a repo ends up "enforcing 80%" in a doc and
|
|
50
|
+
// 60% in reality. Reporting the conflict is the only honest answer.
|
|
51
|
+
const r = resolveCoverageThreshold({
|
|
52
|
+
vitest: { lines: 60, statements: 60, functions: 81, branches: 79 },
|
|
53
|
+
preCommit: 80,
|
|
54
|
+
});
|
|
55
|
+
expect(r.conflict).toBeTruthy();
|
|
56
|
+
expect(r.conflict.hook).toBe(80);
|
|
57
|
+
expect(r.conflict.vitest).toBe(60);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('AC-605-03: 两条闸门一致时不算冲突', () => {
|
|
61
|
+
const r = resolveCoverageThreshold({ vitest: { lines: 80 }, preCommit: 80 });
|
|
62
|
+
expect(r.conflict).toBeFalsy();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('AC-605-02: 冲突时两端数值都可单独查询,便于决定改哪边', () => {
|
|
66
|
+
// The live state: `npx vitest run` goes green at 60% locally, then the commit
|
|
67
|
+
// is blocked at 80%. That ordering is the worst one -- it reads as "my change
|
|
68
|
+
// is fine" right up until it is rejected. Reporting both numbers lets the
|
|
69
|
+
// reader see which declaration to move.
|
|
70
|
+
const r = resolveCoverageThreshold({ vitest: { lines: 60 }, preCommit: 80 });
|
|
71
|
+
expect(r.conflict.hook).toBe(80);
|
|
72
|
+
expect(r.conflict.vitest).toBe(60);
|
|
73
|
+
expect(describeThreshold(r)).toContain('CONFLICT');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('AC-605-01: describeThreshold 让来源可被人类核对', () => {
|
|
77
|
+
expect(describeThreshold(resolveCoverageThreshold({}))).toContain('implicit');
|
|
78
|
+
expect(describeThreshold(resolveCoverageThreshold({ vitest: { lines: 75 } }))).toContain('vitest.config.ts');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-638 Boy Scout 债务必须按"可修性"分级,而不是报一个总数
|
|
3
|
+
* @intent #638 追踪单把 Gate 6 的债务记作「139 条预算告警 / 14 文件」,并归类为
|
|
4
|
+
* "可清零"。实测这 139 条并不构成同一类问题:
|
|
5
|
+
*
|
|
6
|
+
* magic-numbers 122 条(INFO 级 —— 按 .principlesrc 设计就不阻断)
|
|
7
|
+
* missing-error-handling 79 条(可修,且值得修)
|
|
8
|
+
* long-function 27 条(可修,需重构)
|
|
9
|
+
* deep-nesting 22 条(可修)
|
|
10
|
+
* solid.* / 其它 11 条
|
|
11
|
+
*
|
|
12
|
+
* (合计 261 > 139,因为部分文件在 baseline 里已过期。)
|
|
13
|
+
*
|
|
14
|
+
* **把 INFO 级规则算进"债务"会制造一个永远清不完的数字**:magic-numbers
|
|
15
|
+
* 在测试里几乎是不可避免的(超时值、端口、状态码),它被设计成 INFO 正是因为
|
|
16
|
+
* 不该逼人去改。混在一起统计的后果是"债务 139 条"这个数字既不能指导行动,
|
|
17
|
+
* 也不能衡量进展——修掉 79 条 missing-error-handling 后,数字只从 139 掉到 60,
|
|
18
|
+
* 看起来像只完成了 57%,实际已经清空了最值得清的那一类。
|
|
19
|
+
*
|
|
20
|
+
* 所以分级的价值不是"更好看",而是让"还剩多少真正要修"变成可回答的问题。
|
|
21
|
+
* @covers AC-638-01, AC-638-02, AC-638-03
|
|
22
|
+
*/
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const { classifyDebt, summarizeDebtByFixability, INFO_ONLY_RULES } = require('../debt-classify');
|
|
26
|
+
|
|
27
|
+
describe('Boy Scout 债务分级 (#638)', () => {
|
|
28
|
+
it('AC-638-01: INFO 级规则不计入"需要修"的债务', () => {
|
|
29
|
+
// .principlesrc marks magic-numbers as severity=info. A rule that never blocks
|
|
30
|
+
// anything is not a debt to clear; counting it makes the number unchaseable.
|
|
31
|
+
expect(INFO_ONLY_RULES).toContain('clean-code.magic-numbers');
|
|
32
|
+
const buckets = summarizeDebtByFixability([
|
|
33
|
+
{ file: 'a.ts', rule: 'clean-code.magic-numbers', count: 122 },
|
|
34
|
+
{ file: 'b.ts', rule: 'clean-code.missing-error-handling', count: 79 },
|
|
35
|
+
]);
|
|
36
|
+
expect(buckets.actionable).toBe(79);
|
|
37
|
+
expect(buckets.informational).toBe(122);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('AC-638-02: 可修类别各自独立计数,因为修法不同', () => {
|
|
41
|
+
// "Fix the debt" is four different jobs: add try/catch, split a function,
|
|
42
|
+
// flatten nesting, split a class. Collapsing them hides which job is left.
|
|
43
|
+
const buckets = summarizeDebtByFixability([
|
|
44
|
+
{ file: 'a.ts', rule: 'clean-code.missing-error-handling', count: 79 },
|
|
45
|
+
{ file: 'a.ts', rule: 'clean-code.long-function', count: 27 },
|
|
46
|
+
{ file: 'b.ts', rule: 'clean-code.deep-nesting', count: 22 },
|
|
47
|
+
{ file: 'b.ts', rule: 'solid.srp', count: 5 },
|
|
48
|
+
]);
|
|
49
|
+
expect(buckets.byRule['clean-code.missing-error-handling']).toBe(79);
|
|
50
|
+
expect(buckets.byRule['clean-code.long-function']).toBe(27);
|
|
51
|
+
expect(buckets.byRule['clean-code.deep-nesting']).toBe(22);
|
|
52
|
+
expect(buckets.byRule['solid.srp']).toBe(5);
|
|
53
|
+
expect(buckets.actionable).toBe(133);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('AC-638-03: 分级对未知规则保守处理——算作可修,不算作噪音', () => {
|
|
57
|
+
// A rule added to the analyzer must not silently become "informational" just
|
|
58
|
+
// because this module has not heard of it. Defaulting to actionable over-reports
|
|
59
|
+
// work; defaulting to info under-reports debt, which is the failure that lets
|
|
60
|
+
// real problems hide behind a green dashboard.
|
|
61
|
+
const buckets = summarizeDebtByFixability([
|
|
62
|
+
{ file: 'a.ts', rule: 'clean-code.brand-new-rule', count: 3 },
|
|
63
|
+
]);
|
|
64
|
+
expect(buckets.actionable).toBe(3);
|
|
65
|
+
expect(buckets.informational).toBe(0);
|
|
66
|
+
expect(buckets.byRule['clean-code.brand-new-rule']).toBe(3);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('AC-638-03: 单条 finding 数组也能分级', () => {
|
|
70
|
+
const rows = classifyDebt([
|
|
71
|
+
{ file: 'a.ts', rule: 'clean-code.magic-numbers' },
|
|
72
|
+
{ file: 'a.ts', rule: 'clean-code.long-function' },
|
|
73
|
+
]);
|
|
74
|
+
expect(rows).toHaveLength(2);
|
|
75
|
+
expect(rows[0].fixability).toBe('informational');
|
|
76
|
+
expect(rows[1].fixability).toBe('actionable');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage-threshold resolution with an explicit provenance field (#605).
|
|
3
|
+
*
|
|
4
|
+
* #605 asks for a single machine-readable test profile because thresholds are
|
|
5
|
+
* scattered across 4+ files. Measured, the scattering is not the damaging part —
|
|
6
|
+
* the damaging part is that the same concept has two live gates that read the same
|
|
7
|
+
* number and disagree:
|
|
8
|
+
*
|
|
9
|
+
* githooks/pre-commit ${COVERAGE_THRESHOLD:-80} flat 80%
|
|
10
|
+
* vitest.config.ts lines 60 / statements 60
|
|
11
|
+
* functions 81 / branches 79
|
|
12
|
+
*
|
|
13
|
+
* The vitest block documents itself as "the measured floor, not the 80% target",
|
|
14
|
+
* so both are locally reasonable. The repo-wide consequence is that nobody can
|
|
15
|
+
* answer "what coverage does this repo require" without reading two files.
|
|
16
|
+
*
|
|
17
|
+
* This module does not try to collapse every threshold into one file. It makes the
|
|
18
|
+
* answer to that one question unique and explainable: given what each source
|
|
19
|
+
* declares, produce (value, source, declared) and report a conflict rather than
|
|
20
|
+
* silently preferring one side.
|
|
21
|
+
*
|
|
22
|
+
* The `declared` flag exists because of #561: `:-80` made "nobody declared a
|
|
23
|
+
* threshold" and "someone declared 80" indistinguishable, so a doc claiming 80%
|
|
24
|
+
* while the live config said 60% raised no alarm anywhere.
|
|
25
|
+
*/
|
|
26
|
+
'use strict';
|
|
27
|
+
|
|
28
|
+
/** What the hook assumes when no config declares a coverage floor. */
|
|
29
|
+
const IMPLICIT_COVERAGE_FLOOR = 80;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the coverage floor from the sources that actually declare one.
|
|
33
|
+
*
|
|
34
|
+
* @param {object} [sources]
|
|
35
|
+
* @param {{lines?: number, statements?: number, functions?: number, branches?: number}} [sources.vitest]
|
|
36
|
+
* @param {number} [sources.preCommit]
|
|
37
|
+
* @returns {{value: number, source: string, declared: boolean, conflict?: {hook: number, vitest: number, detail: string}}}
|
|
38
|
+
*/
|
|
39
|
+
function resolveCoverageThreshold(sources) {
|
|
40
|
+
const src = sources || {};
|
|
41
|
+
const vitestLines = src.vitest && typeof src.vitest.lines === 'number' ? src.vitest.lines : null;
|
|
42
|
+
const hookValue = typeof src.preCommit === 'number' ? src.preCommit : null;
|
|
43
|
+
|
|
44
|
+
const conflict = detectConflict(vitestLines, hookValue);
|
|
45
|
+
|
|
46
|
+
if (vitestLines !== null) {
|
|
47
|
+
return {
|
|
48
|
+
value: vitestLines,
|
|
49
|
+
source: 'vitest.config.ts',
|
|
50
|
+
declared: true,
|
|
51
|
+
...(conflict ? { conflict } : {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (hookValue !== null) {
|
|
56
|
+
return { value: hookValue, source: 'pre-commit', declared: true, ...(conflict ? { conflict } : {}) };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Nothing declared: the hook's fallback applies. `declared: false` is the whole
|
|
60
|
+
// point -- a caller must be able to tell "80 because someone chose 80" from
|
|
61
|
+
// "80 because nobody said anything".
|
|
62
|
+
return { value: IMPLICIT_COVERAGE_FLOOR, source: 'implicit', declared: false, ...(conflict ? { conflict } : {}) };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Two live gates disagreeing about the same number is a defect, not a preference.
|
|
67
|
+
* Reported, never resolved silently.
|
|
68
|
+
*/
|
|
69
|
+
function detectConflict(vitestLines, hookValue) {
|
|
70
|
+
if (vitestLines === null || hookValue === null) return null;
|
|
71
|
+
if (vitestLines === hookValue) return null;
|
|
72
|
+
return {
|
|
73
|
+
hook: hookValue,
|
|
74
|
+
vitest: vitestLines,
|
|
75
|
+
detail: `pre-commit enforces ${hookValue}% while vitest.config.ts enforces ${vitestLines}%`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** One-line, human-checkable rendering of a resolved threshold. */
|
|
80
|
+
function describeThreshold(resolved) {
|
|
81
|
+
if (!resolved) return 'coverage threshold: unknown';
|
|
82
|
+
const parts = [`coverage threshold: ${resolved.value}%`, `source: ${resolved.source}`];
|
|
83
|
+
if (!resolved.declared) parts.push('not declared by any config — this is the hook fallback');
|
|
84
|
+
if (resolved.conflict) parts.push(`CONFLICT — ${resolved.conflict.detail}`);
|
|
85
|
+
return parts.join(' | ');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { resolveCoverageThreshold, describeThreshold, IMPLICIT_COVERAGE_FLOOR };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify Boy Scout / principles debt by whether it is worth fixing (#638).
|
|
3
|
+
*
|
|
4
|
+
* The tracker reported Gate 6 as "139 warnings / 14 files — clearable". Measured,
|
|
5
|
+
* those warnings are not one kind of problem:
|
|
6
|
+
*
|
|
7
|
+
* magic-numbers 122 INFO — `.principlesrc` sets severity=info, so it
|
|
8
|
+
* never blocks. Almost unavoidable in tests
|
|
9
|
+
* (timeouts, ports, status codes) and deliberately
|
|
10
|
+
* not worth forcing people to edit.
|
|
11
|
+
* missing-error-handling 79 actionable
|
|
12
|
+
* long-function 27 actionable
|
|
13
|
+
* deep-nesting 22 actionable
|
|
14
|
+
* solid.* + misc 11 actionable
|
|
15
|
+
*
|
|
16
|
+
* Mixing them makes the headline number un-actionable: clearing every
|
|
17
|
+
* missing-error-handling drops the total from 139 to 60, which reads like 57% done
|
|
18
|
+
* when the most valuable category is actually empty. A number that cannot express
|
|
19
|
+
* progress cannot direct work.
|
|
20
|
+
*
|
|
21
|
+
* So this module splits debt into "actionable" and "informational", and keeps the
|
|
22
|
+
* actionable rules separate because each is a different job — add a try/catch, split
|
|
23
|
+
* a function, flatten nesting, split a class.
|
|
24
|
+
*/
|
|
25
|
+
'use strict';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Rules whose configured severity is `info`.
|
|
29
|
+
*
|
|
30
|
+
* These still appear in analyzer output, but `.principlesrc` marks them info
|
|
31
|
+
* precisely so they never block. Listing them as debt would make the debt number
|
|
32
|
+
* permanently non-zero.
|
|
33
|
+
*/
|
|
34
|
+
const INFO_ONLY_RULES = [
|
|
35
|
+
'clean-code.magic-numbers',
|
|
36
|
+
'clean-code.unused-imports',
|
|
37
|
+
'clean-code.many-exports',
|
|
38
|
+
'solid.ocp',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function isInformational(rule) {
|
|
42
|
+
return INFO_ONLY_RULES.includes(rule);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Annotate individual findings with `fixability`.
|
|
47
|
+
*
|
|
48
|
+
* Unknown rules default to `actionable` on purpose. A new analyzer rule that this
|
|
49
|
+
* module has not heard of must not silently become noise — over-reporting work is
|
|
50
|
+
* recoverable, under-reporting debt is the failure mode that lets real problems hide
|
|
51
|
+
* behind a green dashboard.
|
|
52
|
+
*
|
|
53
|
+
* @param {Array<{file: string, rule: string}>} findings
|
|
54
|
+
* @returns {Array<{file: string, rule: string, fixability: 'actionable'|'informational'}>}
|
|
55
|
+
*/
|
|
56
|
+
function classifyDebt(findings) {
|
|
57
|
+
if (!Array.isArray(findings)) return [];
|
|
58
|
+
return findings.map((f) => ({
|
|
59
|
+
...f,
|
|
60
|
+
fixability: isInformational(f.rule) ? 'informational' : 'actionable',
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Totals per fixability class, plus a per-rule breakdown of the actionable part.
|
|
66
|
+
*
|
|
67
|
+
* @param {Array<{file: string, rule: string, count?: number}>} rows
|
|
68
|
+
* @returns {{actionable: number, informational: number, byRule: Record<string, number>}}
|
|
69
|
+
*/
|
|
70
|
+
function summarizeDebtByFixability(rows) {
|
|
71
|
+
const byRule = {};
|
|
72
|
+
let actionable = 0;
|
|
73
|
+
let informational = 0;
|
|
74
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
75
|
+
const count = typeof row.count === 'number' ? row.count : 1;
|
|
76
|
+
if (isInformational(row.rule)) {
|
|
77
|
+
informational += count;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
actionable += count;
|
|
81
|
+
byRule[row.rule] = (byRule[row.rule] || 0) + count;
|
|
82
|
+
}
|
|
83
|
+
return { actionable, informational, byRule };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { classifyDebt, summarizeDebtByFixability, INFO_ONLY_RULES, isInformational };
|
package/lib/gate-coverage.js
CHANGED
|
@@ -151,14 +151,8 @@ function buildCoverageRows(artifact, ctx) {
|
|
|
151
151
|
};
|
|
152
152
|
|
|
153
153
|
if (!entry) {
|
|
154
|
-
// Not in the artifact at all. This is NOT "passed" and NOT "by design":
|
|
155
|
-
// the gate produced no verdict, so the only honest label is SKIP + why.
|
|
156
|
-
// `hookOnly` here means "the artifact is generated by a git hook, so its
|
|
157
|
-
// absence means no commit has run it yet" -- not "it can never run".
|
|
158
154
|
row.status = STATUS.SKIP;
|
|
159
|
-
row.reason = spec
|
|
160
|
-
? 'no result recorded — runs via git commit'
|
|
161
|
-
: 'no result recorded — not run in the last report';
|
|
155
|
+
row.reason = missingGateReason(spec);
|
|
162
156
|
return row;
|
|
163
157
|
}
|
|
164
158
|
|
|
@@ -183,7 +177,7 @@ function buildCoverageRows(artifact, ctx) {
|
|
|
183
177
|
return {
|
|
184
178
|
...row,
|
|
185
179
|
status: STATUS.DEBT,
|
|
186
|
-
debt:
|
|
180
|
+
debt: describeBoyScoutDebt(context.boyScout),
|
|
187
181
|
evidence: 'warnings baseline (see .warnings-baseline.json)',
|
|
188
182
|
};
|
|
189
183
|
}
|
|
@@ -191,6 +185,92 @@ function buildCoverageRows(artifact, ctx) {
|
|
|
191
185
|
});
|
|
192
186
|
}
|
|
193
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Why a gate produced no verdict.
|
|
190
|
+
*
|
|
191
|
+
* Not in the artifact at all. This is NOT "passed" and NOT "by design": the gate
|
|
192
|
+
* produced no verdict, so the only honest label is SKIP + why. `hookOnly` here means
|
|
193
|
+
* "the artifact is generated by a git hook, so its absence means no commit has run
|
|
194
|
+
* it yet" -- not "it can never run".
|
|
195
|
+
*/
|
|
196
|
+
function missingGateReason(spec) {
|
|
197
|
+
return spec.hookOnly
|
|
198
|
+
? 'no result recorded — runs via git commit'
|
|
199
|
+
: 'no result recorded — not run in the last report';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Resolve the coverage floor from both gates and report disagreement (#605).
|
|
204
|
+
*
|
|
205
|
+
* Read from source rather than cached: both files are small, and a stale threshold
|
|
206
|
+
* report is worse than none -- it would assert the gates agree when they need not.
|
|
207
|
+
* Either side absent degrades to "unknown", never to "no conflict".
|
|
208
|
+
*/
|
|
209
|
+
function readCoverageThresholdConflict(repoRoot) {
|
|
210
|
+
try {
|
|
211
|
+
const { resolveCoverageThreshold } = require('./coverage-threshold.js');
|
|
212
|
+
const sources = {};
|
|
213
|
+
const vitest = readVitestThresholds(repoRoot);
|
|
214
|
+
if (vitest) sources.vitest = vitest;
|
|
215
|
+
const hookFloor = readHookCoverageFloor(repoRoot);
|
|
216
|
+
if (hookFloor !== null) sources.preCommit = hookFloor;
|
|
217
|
+
const resolved = resolveCoverageThreshold(sources);
|
|
218
|
+
if (!vitest && hookFloor === null) return { ...resolved, sources: 'none readable' };
|
|
219
|
+
return resolved;
|
|
220
|
+
} catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* `thresholds: { lines: N, ... }` from vitest.config.ts, or null when absent.
|
|
227
|
+
*
|
|
228
|
+
* Comments are stripped first. The file documents the #561 failure by quoting the
|
|
229
|
+
* old bad shape (`thresholds: { global: {...} }`) in prose, and a naive match reads
|
|
230
|
+
* that quote as the live config -- reporting a threshold the repo does not use,
|
|
231
|
+
* which is exactly the class of error #605 exists to stop.
|
|
232
|
+
*/
|
|
233
|
+
function readVitestThresholds(repoRoot) {
|
|
234
|
+
try {
|
|
235
|
+
const raw = fs.readFileSync(path.join(repoRoot, 'vitest.config.ts'), 'utf8');
|
|
236
|
+
const code = raw.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
|
237
|
+
const block = code.match(/thresholds:\s*\{([\s\S]*?)\}/);
|
|
238
|
+
if (!block) return null;
|
|
239
|
+
const out = {};
|
|
240
|
+
for (const m of block[1].matchAll(/(lines|statements|functions|branches)\s*:\s*(\d+)/g)) {
|
|
241
|
+
out[m[1]] = Number(m[2]);
|
|
242
|
+
}
|
|
243
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** The flat floor the pre-commit hook applies, or null when it applies none. */
|
|
250
|
+
function readHookCoverageFloor(repoRoot) {
|
|
251
|
+
try {
|
|
252
|
+
const raw = fs.readFileSync(path.join(repoRoot, 'githooks', 'pre-commit'), 'utf8');
|
|
253
|
+
if (!/COVERAGE_THRESHOLD/.test(raw)) return null;
|
|
254
|
+
const m = raw.match(/COVERAGE_THRESHOLD:-?(\d+)/) || raw.match(/COVERAGE_THRESHOLD=(\d+)/);
|
|
255
|
+
return m ? Number(m[1]) : null;
|
|
256
|
+
} catch {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Describe Boy Scout debt by how much of it is worth fixing (#638).
|
|
263
|
+
*
|
|
264
|
+
* `magic-numbers` is severity=info and accounts for roughly half the raw count, so a
|
|
265
|
+
* single total is un-actionable: clearing every real finding barely moves it, and a
|
|
266
|
+
* number that cannot express progress cannot direct work.
|
|
267
|
+
*/
|
|
268
|
+
function describeBoyScoutDebt(boyScout) {
|
|
269
|
+
const breakdown = boyScout.breakdown;
|
|
270
|
+
if (!breakdown) return `${boyScout.warnings} warning(s) / ${boyScout.files} file(s)`;
|
|
271
|
+
return `${breakdown.actionable} actionable / ${breakdown.informational} informational (${boyScout.files} file(s))`;
|
|
272
|
+
}
|
|
273
|
+
|
|
194
274
|
/** Render rows as the human-readable overview. */
|
|
195
275
|
function renderGateCoverage(rows, opts) {
|
|
196
276
|
const options = opts || {};
|
|
@@ -212,6 +292,16 @@ function renderGateCoverage(rows, opts) {
|
|
|
212
292
|
lines.push(' Numbers below describe that commit, NOT the working tree.');
|
|
213
293
|
}
|
|
214
294
|
|
|
295
|
+
if (options.coverageThreshold && options.coverageThreshold.conflict) {
|
|
296
|
+
// #605: two live gates enforcing different numbers for the same thing. Reported
|
|
297
|
+
// here rather than resolved silently -- picking one side is how a repo ends up
|
|
298
|
+
// documented at 80% and enforced at 60%.
|
|
299
|
+
lines.push('');
|
|
300
|
+
lines.push(` ⚠️ COVERAGE THRESHOLD CONFLICT — ${options.coverageThreshold.conflict.detail}`);
|
|
301
|
+
lines.push(' Both gates read the same number and disagree. Fix the declarations,');
|
|
302
|
+
lines.push(' not the reporting.');
|
|
303
|
+
}
|
|
304
|
+
|
|
215
305
|
lines.push('');
|
|
216
306
|
lines.push(` ${counts.PASS} PASS / ${counts.DEBT} DEBT / ${counts.SKIP} SKIP / ${counts.FAIL} FAIL`);
|
|
217
307
|
if (counts.SKIP > 0) {
|
|
@@ -256,8 +346,61 @@ function readBoyScoutDebt(repoRoot) {
|
|
|
256
346
|
const value = baseline[key];
|
|
257
347
|
warnings += typeof value === 'number' ? value : (value && value.totalWarnings) || 0;
|
|
258
348
|
}
|
|
259
|
-
return { files: files.length, warnings };
|
|
349
|
+
return { files: files.length, warnings, breakdown: readDebtBreakdown(repoRoot, files) };
|
|
350
|
+
} catch {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Rule-level breakdown of the Boy Scout debt (#638).
|
|
357
|
+
*
|
|
358
|
+
* The baseline stores only `totalWarnings` per file, so the split cannot be derived
|
|
359
|
+
* from it. It also must not be recomputed on every `gate-coverage` call: analysing
|
|
360
|
+
* 17 files through the principles engine costs ~90s, and a report command that takes
|
|
361
|
+
* a minute and a half stops being run.
|
|
362
|
+
*
|
|
363
|
+
* Hence a cache keyed on the baseline file's mtime+size: any commit that changes the
|
|
364
|
+
* debt changes the baseline, which invalidates the cache. A stale split is worse
|
|
365
|
+
* than no split, because it would report progress that did not happen.
|
|
366
|
+
*/
|
|
367
|
+
function readDebtBreakdown(repoRoot, files) {
|
|
368
|
+
try {
|
|
369
|
+
const { summarizeDebtByFixability } = require('./debt-classify.js');
|
|
370
|
+
const baselinePath = path.join(repoRoot, '.warnings-baseline.json');
|
|
371
|
+
const stat = fs.statSync(baselinePath);
|
|
372
|
+
const cachePath = path.join(repoRoot, '.xp-gate', 'debt-breakdown.json');
|
|
373
|
+
const cacheKey = `${stat.mtimeMs}:${stat.size}`;
|
|
374
|
+
try {
|
|
375
|
+
const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
376
|
+
if (cached && cached.key === cacheKey && cached.breakdown) return cached.breakdown;
|
|
377
|
+
} catch {
|
|
378
|
+
// No usable cache -- fall through and recompute.
|
|
379
|
+
}
|
|
380
|
+
const { execFileSync } = require('node:child_process');
|
|
381
|
+
const targets = files.filter((f) => !f.includes('/src/npm-package/'));
|
|
382
|
+
const raw = execFileSync(
|
|
383
|
+
process.execPath,
|
|
384
|
+
[path.join(repoRoot, 'node_modules', 'tsx', 'dist', 'cli.mjs'),
|
|
385
|
+
path.join(repoRoot, 'src', 'principles', 'index.ts'),
|
|
386
|
+
'--files', targets.join(' '), '--format', 'json'],
|
|
387
|
+
{ cwd: repoRoot, encoding: 'utf8', timeout: 180000 },
|
|
388
|
+
);
|
|
389
|
+
const parsed = JSON.parse(raw);
|
|
390
|
+
const rows = Object.entries(parsed.ruleResults || {}).map(([rule, v]) => ({
|
|
391
|
+
rule,
|
|
392
|
+
count: v.violationCount || 0,
|
|
393
|
+
}));
|
|
394
|
+
const breakdown = summarizeDebtByFixability(rows);
|
|
395
|
+
try {
|
|
396
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
397
|
+
fs.writeFileSync(cachePath, JSON.stringify({ key: cacheKey, breakdown }));
|
|
398
|
+
} catch {
|
|
399
|
+
// A cache write failure must not break the report.
|
|
400
|
+
}
|
|
401
|
+
return breakdown;
|
|
260
402
|
} catch {
|
|
403
|
+
// Degrade to the plain total rather than failing the whole report.
|
|
261
404
|
return null;
|
|
262
405
|
}
|
|
263
406
|
}
|
|
@@ -278,6 +421,7 @@ function main(argv, deps) {
|
|
|
278
421
|
headCommit,
|
|
279
422
|
boyScout: readBoyScoutDebt(repoRoot),
|
|
280
423
|
});
|
|
424
|
+
const coverageThreshold = readCoverageThresholdConflict(repoRoot);
|
|
281
425
|
|
|
282
426
|
if (args.includes('--json')) {
|
|
283
427
|
process.stdout.write(`${JSON.stringify({
|
|
@@ -287,6 +431,7 @@ function main(argv, deps) {
|
|
|
287
431
|
headCommit: headCommit || null,
|
|
288
432
|
artifactFile: file,
|
|
289
433
|
summary: rows.reduce((acc, r) => { acc[r.status] = (acc[r.status] || 0) + 1; return acc; }, {}),
|
|
434
|
+
coverageThreshold,
|
|
290
435
|
gates: rows,
|
|
291
436
|
}, null, 2)}\n`);
|
|
292
437
|
return exitCodeFor(rows, { strict: args.includes('--strict') });
|
|
@@ -299,6 +444,7 @@ function main(argv, deps) {
|
|
|
299
444
|
stale,
|
|
300
445
|
headCommit,
|
|
301
446
|
artifactCommit: artifact ? artifact.commit : null,
|
|
447
|
+
coverageThreshold,
|
|
302
448
|
})}\n`);
|
|
303
449
|
return exitCodeFor(rows, { strict: args.includes('--strict') });
|
|
304
450
|
}
|
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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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.10",
|
|
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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
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.10",
|
|
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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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.10",
|
|
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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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.
|
|
@@ -34,12 +34,25 @@
|
|
|
34
34
|
|---|------|------------------------------|----------|
|
|
35
35
|
| 1 | **Gate 3 CCN** | 单函数圈复杂度 **>10 BLOCK**、**>5 WARN** | 拆函数 / 早返回 / 抽策略表;`xp-gate check 3 <file>` |
|
|
36
36
|
| 2 | **Gate 6 Boy Scout** | **新文件 0 warning**;修改文件 warning 数不得高于 `.warnings-baseline.json`;**单函数 ≤50 行**;**嵌套 ≤4 层** | 新文件当"零容忍"写;改动前 `xp-gate check 6` 看基线 |
|
|
37
|
-
| 3 | **Gate 2 重复代码** | jscpd
|
|
37
|
+
| 3 | **Gate 2 重复代码** | jscpd 报告重复**仅提示,不阻断** | 抽公共函数,别复制粘贴;`xp-gate check 2 <dir>` |
|
|
38
38
|
| 4 | **Gate 4 Principles** | 14 条 Clean Code/SOLID 规则,任一 error/warning 即拦(按 `.principlesrc`) | `xp-gate check 4 <file>`;避免超长参数表、空 catch、`as any` |
|
|
39
39
|
| 5 | **Gate 1 Lint** | 适配器 lint 任一 error | 提交前跑一次项目 lint;不要留未用变量/导入 |
|
|
40
40
|
| 6 | **Gate 5 测试** | 测试全绿 + 覆盖率 ≥80%(新 .ts/.tsx 无测试直接 BLOCK) | 新代码先写测试(TDD);`npx vitest run <file>` |
|
|
41
41
|
| 7 | **mirror/sync 纪律** | 改 `githooks/` 或 `skills/` 后必须 `node src/npm-package/scripts/sync-package-content.js --fix` 且 `bash scripts/check-hook-mirror.sh` 报 PASS | 忘了同步会在 pre-push 的 mirror 校验被打回 |
|
|
42
42
|
|
|
43
|
+
**权威清单用 `xp-gate gate-rules` 现取,不要背上面这张表。** 上表是手抄的,阈值真正定义在
|
|
44
|
+
`.principlesrc`、`jscpd.conf.json` 和 `githooks/pre-commit` 里。`xp-gate gate-rules` 从那些
|
|
45
|
+
文件生成一份带**出处**的清单:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
xp-gate gate-rules # markdown 表:规则 / 严重度 / 阈值 / 出处
|
|
49
|
+
xp-gate gate-rules --json # 机器可读
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
为什么必须生成而不是手写:这张表原本写"jscpd 相似度 >5% BLOCK",而 Gate 2 实际只是提示、
|
|
53
|
+
从不阻断——有人会去为一个永不触发的裁定重构代码。**一个说谎的自检清单比没有清单更糟**,
|
|
54
|
+
它会让人按错误规则写,然后在提交时被告知另一套。改阈值时只需改配置,清单自动跟上。
|
|
55
|
+
|
|
43
56
|
**一条命令抢先看**:`xp-gate check . --changed --brief` 只跑"本次 diff 会触发的门禁",
|
|
44
57
|
一屏出 PASS/SKIP/BLOCK,目标 <5 s。把它放在 ralph-loop 每个 REQ 的 step 0,先看红不红,
|
|
45
58
|
再决定是否继续写——而不是写到一半才被 `git commit` 拦下。
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.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:** 6c9bcb78
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.10.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|