@boyingliu01/xp-gate 0.35.6 → 0.35.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/bin/xp-gate.js +60 -0
  2. package/hooks/lib/assertion-quality.sh +47 -0
  3. package/hooks/pre-commit +54 -8
  4. package/lib/__tests__/assertion-debt.test.js +99 -0
  5. package/lib/__tests__/gate-rules.test.js +58 -0
  6. package/lib/assertion-debt.js +173 -0
  7. package/lib/gate-rules.js +149 -0
  8. package/lib/mattpocock-license-verdict.json +1 -1
  9. package/mock-policy/AGENTS.md +2 -2
  10. package/mutation/AGENTS.md +2 -2
  11. package/package.json +1 -1
  12. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  13. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  14. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  15. package/plugins/claude-code/skills/sprint-flow/references/phase-3-build.md +14 -1
  16. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  17. package/plugins/dsh/package.json +1 -1
  18. package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
  19. package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
  20. package/plugins/dsh/skills/sprint-flow/references/phase-3-build.md +14 -1
  21. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
  22. package/plugins/opencode/package.json +1 -1
  23. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  24. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  25. package/plugins/opencode/skills/sprint-flow/references/phase-3-build.md +14 -1
  26. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  27. package/plugins/qoder/plugin.json +1 -1
  28. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  29. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  30. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  31. package/principles/AGENTS.md +2 -2
  32. package/skills/delphi-review/AGENTS.md +2 -2
  33. package/skills/sprint-flow/AGENTS.md +2 -2
  34. package/skills/sprint-flow/references/phase-3-build.md +14 -1
  35. package/skills/test-specification-alignment/AGENTS.md +2 -2
package/bin/xp-gate.js CHANGED
@@ -365,6 +365,21 @@ const COMMANDS = {
365
365
  },
366
366
  usage: 'xp-gate gate-coverage [--json] [--debt] [--strict] [--branch <name>]'
367
367
  },
368
+ 'gate-rules': {
369
+ description: 'Print the gate limits that can block a commit, generated from the files that define them (#652)',
370
+ run: subargs => {
371
+ const { collectGateRules, renderGateRules } = require('../lib/gate-rules.js');
372
+ const rules = collectGateRules({ cwd: process.cwd() });
373
+ if (subargs.includes('--json')) {
374
+ process.stdout.write(`${JSON.stringify(rules, null, 2)}\n`);
375
+ return;
376
+ }
377
+ // Generated, never hand-copied: a checklist that drifts from the config it
378
+ // describes teaches the wrong rule and then contradicts it at commit time.
379
+ process.stdout.write(`${renderGateRules(rules)}\n`);
380
+ },
381
+ usage: 'xp-gate gate-rules [--json]'
382
+ },
368
383
  'rework': {
369
384
  description: 'Report rework: first-pass rate, rounds before landing, top blocking reasons (#651)',
370
385
  run: subargs => {
@@ -673,15 +688,60 @@ function handleAudit(args) {
673
688
  return;
674
689
  }
675
690
 
691
+ // #668: pre-existing assertion debt, so it can be cleared in batches instead of
692
+ // being discovered one unrelated commit at a time.
693
+ if (args[0] === 'assertions') {
694
+ handleAuditAssertions(args);
695
+ return;
696
+ }
697
+
676
698
  console.error('Error: Unknown audit subcommand or missing flag');
677
699
  console.error('Usage:');
678
700
  console.error(' xp-gate audit record --gate-id X --gate-name Y --passed true/false --issues-found N --duration-ms N --trigger commit');
679
701
  console.error(' xp-gate audit --tail [N] (default: 20)');
680
702
  console.error(' xp-gate audit --stats (per-gate stats + anomaly summary)');
681
703
  console.error(' xp-gate audit --check (exit 1 if the ledger has anomalies)');
704
+ console.error(' xp-gate audit assertions (list pre-existing assertion debt across the repo; --json)');
682
705
  process.exit(1);
683
706
  }
684
707
 
708
+ /**
709
+ * `xp-gate audit assertions` (#668) — list pre-existing vacuous assertions.
710
+ *
711
+ * Gate 5d only inspects files in the diff, so standing debt surfaces during
712
+ * unrelated commits and reads as if that change introduced it. This makes the debt
713
+ * a work queue instead of a series of false accusations.
714
+ *
715
+ * Advisory: always exits 0. A debt list that fails builds becomes a number people
716
+ * learn to ignore.
717
+ */
718
+ function handleAuditAssertions(args) {
719
+ const { scanForAssertionDebt, summarizeDebt } = require(path.join(__dirname, '..', 'lib', 'assertion-debt.js'));
720
+ const results = scanForAssertionDebt({ cwd: process.cwd() });
721
+ const summary = summarizeDebt(results);
722
+ if (args.includes('--json')) {
723
+ process.stdout.write(`${JSON.stringify({ summary, files: results }, null, 2)}\n`);
724
+ return;
725
+ }
726
+ if (summary.total === 0) {
727
+ console.log('✅ No pre-existing assertion debt found.');
728
+ return;
729
+ }
730
+ console.log(`Found ${summary.total} pre-existing assertion issue(s) in ${summary.files} file(s):`);
731
+ console.log(` tautological ${summary.byKind.tautological}`);
732
+ console.log(` swallowed-error ${summary.byKind['swallowed-error']}`);
733
+ console.log('');
734
+ for (const entry of results) {
735
+ console.log(`${entry.file} (${entry.findings.length})`);
736
+ for (const finding of entry.findings) {
737
+ console.log(` ${finding.line}: [${finding.kind}] ${finding.text}`);
738
+ }
739
+ }
740
+ console.log('');
741
+ console.log('These predate the current diff and would be reported as "introduced by this change"');
742
+ console.log('if the file is touched. See skills/test-driven-development/testing-anti-patterns.md');
743
+ }
744
+
685
745
  function printAuditTable(entries) {
686
746
  console.log('');
687
747
  console.log('Recent Audit Entries:');
@@ -178,6 +178,53 @@ classify_swallowed_errors() {
178
178
  grep -nE 'catch([[:space:]]*\([^)]*\))?[[:space:]]*\{[[:space:]]*\}' || true
179
179
  }
180
180
 
181
+ # #668: is the flagged line part of THIS change, or merely carried in by the file?
182
+ #
183
+ # Gate 5d runs over whole files, so a file that was edited anywhere gets all of its
184
+ # assertions re-examined. Pre-existing debt then blocks an unrelated commit and
185
+ # reads as "this change introduced it". The author who fixed `writeReport()`'s error
186
+ # handling was stopped by four `expect(true).toBe(true)` lines that had been there
187
+ # for months.
188
+ #
189
+ # The cost is not the one false block. It is that the noise teaches people to add
190
+ # `@assertion-justified` rather than fix the test -- a gate that trains people to
191
+ # annotate past it is worse than no gate.
192
+ #
193
+ # Args: $1 = file path, $2 = line number, $3 = added-line ranges ("start-end start-end")
194
+ # Returns 0 when the line is inside an added hunk (i.e. genuinely introduced here).
195
+ assertion_line_is_new() {
196
+ local _aq_file="$1" _aq_line="$2" _aq_ranges="$3"
197
+ local _aq_range _aq_start _aq_end
198
+ for _aq_range in $_aq_ranges; do
199
+ _aq_start="${_aq_range%%-*}"
200
+ _aq_end="${_aq_range##*-}"
201
+ if [ "$_aq_line" -ge "$_aq_start" ] 2>/dev/null && [ "$_aq_line" -le "$_aq_end" ] 2>/dev/null; then
202
+ return 0
203
+ fi
204
+ done
205
+ return 1
206
+ }
207
+
208
+ # Emit "start-end" for each added hunk in the staged diff of $1.
209
+ # Empty output means the file has no added lines at all, in which case every finding
210
+ # is pre-existing by construction.
211
+ staged_added_line_ranges() {
212
+ local _aq_file="$1"
213
+ git diff --cached -U0 -- "$_aq_file" 2>/dev/null \
214
+ | awk '
215
+ /^@@/ {
216
+ # @@ -a,b +c,d @@ -- the +side is the new-file line numbering.
217
+ if (match($0, /\+[0-9]+(,[0-9]+)?/)) {
218
+ spec = substr($0, RSTART + 1, RLENGTH - 1)
219
+ n = split(spec, parts, ",")
220
+ start = parts[1] + 0
221
+ count = (n > 1) ? parts[2] + 0 : 1
222
+ if (count > 0) printf "%d-%d ", start, start + count - 1
223
+ }
224
+ }
225
+ ' || true
226
+ }
227
+
181
228
  # Anti-patterns 3-5 (WARNING tier): weak-only assertions, mock-echo assertions, and
182
229
  # call-order over-verification. These can be legitimate, so each is reported but
183
230
  # never blocks.
package/hooks/pre-commit CHANGED
@@ -2395,17 +2395,63 @@ else
2395
2395
  _tauto=$(printf '%s\n' "$_tf_content" | classify_tautological_assertions || true)
2396
2396
  _swallow=$(printf '%s\n' "$_tf_content" | classify_swallowed_errors || true)
2397
2397
  _weak=$(printf '%s\n' "$_tf_content" | classify_weak_assertions || true)
2398
+ # #668: split the findings into "added by this change" and
2399
+ # "pre-existing". Gate 5d examines whole files, so editing a file
2400
+ # anywhere re-examines all of its assertions; presenting standing debt
2401
+ # under an unrelated commit is a false attribution, and false
2402
+ # attribution is what drives people to @assertion-justified instead of
2403
+ # a real fix.
2404
+ _new_ranges=$(staged_added_line_ranges "$_tf" 2>/dev/null || true)
2398
2405
  if [ -n "$_tauto" ]; then
2399
- echo "❌ BLOCKED - tautological assertion(s) in $_tf (cannot fail):"
2400
- printf '%s\n' "$_tauto" | sed 's/^/ /'
2401
- echo " See skills/test-driven-development/testing-anti-patterns.md"
2402
- GATE_5_FOCUS_BLOCKED=$((GATE_5_FOCUS_BLOCKED + 1))
2406
+ _tauto_new=""
2407
+ _tauto_old=""
2408
+ while IFS= read -r _tline; do
2409
+ [ -n "$_tline" ] || continue
2410
+ _tnum="${_tline%%:*}"
2411
+ if assertion_line_is_new "$_tf" "$_tnum" "$_new_ranges"; then
2412
+ _tauto_new="${_tauto_new}${_tline}"$'\n'
2413
+ else
2414
+ _tauto_old="${_tauto_old}${_tline}"$'\n'
2415
+ fi
2416
+ done <<< "$_tauto"
2417
+ if [ -n "$_tauto_new" ]; then
2418
+ echo "❌ BLOCKED - tautological assertion(s) added by this change in $_tf (cannot fail):"
2419
+ printf '%s' "$_tauto_new" | sed 's/^/ /'
2420
+ echo " See skills/test-driven-development/testing-anti-patterns.md"
2421
+ GATE_5_FOCUS_BLOCKED=$((GATE_5_FOCUS_BLOCKED + 1))
2422
+ fi
2423
+ if [ -n "$_tauto_old" ]; then
2424
+ echo "⚠️ WARN - PRE-EXISTING tautological assertion(s) in $_tf (NOT introduced by this change):"
2425
+ printf '%s' "$_tauto_old" | sed 's/^/ /'
2426
+ echo " These predate this commit. Two options:"
2427
+ echo " fix them now (see 'xp-gate audit assertions'), or"
2428
+ echo " leave them - they are not blocking you."
2429
+ fi
2403
2430
  fi
2404
2431
  if [ -n "$_swallow" ]; then
2405
- echo "❌ BLOCKED - empty catch in $_tf swallows the failure it guards:"
2406
- printf '%s\n' "$_swallow" | sed 's/^/ /'
2407
- echo " See skills/test-driven-development/testing-anti-patterns.md"
2408
- GATE_5_FOCUS_BLOCKED=$((GATE_5_FOCUS_BLOCKED + 1))
2432
+ # Same #668 split as the tautology branch above.
2433
+ _sw_new=""
2434
+ _sw_old=""
2435
+ while IFS= read -r _sline; do
2436
+ [ -n "$_sline" ] || continue
2437
+ _snum="${_sline%%:*}"
2438
+ if assertion_line_is_new "$_tf" "$_snum" "$_new_ranges"; then
2439
+ _sw_new="${_sw_new}${_sline}"$'\n'
2440
+ else
2441
+ _sw_old="${_sw_old}${_sline}"$'\n'
2442
+ fi
2443
+ done <<< "$_swallow"
2444
+ if [ -n "$_sw_new" ]; then
2445
+ echo "❌ BLOCKED - empty catch added by this change in $_tf swallows the failure it guards:"
2446
+ printf '%s' "$_sw_new" | sed 's/^/ /'
2447
+ echo " See skills/test-driven-development/testing-anti-patterns.md"
2448
+ GATE_5_FOCUS_BLOCKED=$((GATE_5_FOCUS_BLOCKED + 1))
2449
+ fi
2450
+ if [ -n "$_sw_old" ]; then
2451
+ echo "⚠️ WARN - PRE-EXISTING empty catch(es) in $_tf (NOT introduced by this change):"
2452
+ printf '%s' "$_sw_old" | sed 's/^/ /'
2453
+ echo " These predate this commit; see 'xp-gate audit assertions'."
2454
+ fi
2409
2455
  fi
2410
2456
  if [ -n "$_weak" ]; then
2411
2457
  echo "⚠️ WARN - test block(s) in $_tf assert only weak properties:"
@@ -0,0 +1,99 @@
1
+ /**
2
+ * @test REQ-668 `xp-gate audit assertions` 必须让存量断言债务可见
3
+ * @intent Gate 5d 只检查 **diff 范围内** 的文件。这是有意的——全仓扫描每次提交都跑
4
+ * 太慢——但它有个昂贵的副作用:存量债务会在 **无关改动** 时突然阻断,
5
+ * 制造错误的因果归因。原作者改了 `writeReport()` 的错误处理,却被
6
+ * `test-alignment.test.ts` 里 4 处既有的 `expect(true).toBe(true)` 拦下。
7
+ *
8
+ * 这个副作用最坏的后果不是那一次误阻断,而是它改变修复者的行为:归因噪音
9
+ * 让人以为"先绕过再说",于是倾向加 `@assertion-justified` 而不是真修掉。
10
+ * 一个让人更愿意豁免的门禁,比没有门禁更糟。
11
+ *
12
+ * 所以 `audit assertions` 的价值在于把"存量"变成可批处理的工作队列,
13
+ * 而不是让人在无关提交里逐个撞见。
14
+ * @covers AC-668-01, AC-668-02, AC-668-03
15
+ */
16
+ 'use strict';
17
+
18
+ const { scanForAssertionDebt, summarizeDebt } = require('../assertion-debt');
19
+
20
+ describe('xp-gate audit assertions (#668)', () => {
21
+ it('AC-668-01: 列出全仓存量问题,按文件分组', () => {
22
+ const results = scanForAssertionDebt({ cwd: process.cwd() });
23
+ expect(Array.isArray(results)).toBe(true);
24
+ // Grouped by file so a batch fixer can work one file at a time.
25
+ for (const entry of results) {
26
+ expect(typeof entry.file).toBe('string');
27
+ expect(Array.isArray(entry.findings)).toBe(true);
28
+ for (const f of entry.findings) {
29
+ expect(typeof f.line).toBe('number');
30
+ expect(['tautological', 'swallowed-error']).toContain(f.kind);
31
+ expect(typeof f.text).toBe('string');
32
+ }
33
+ }
34
+ });
35
+
36
+ it('AC-668-02: 识别两类确定性 vacuous 形态', () => {
37
+ // These are the two shapes Gate 5d blocks. Both are the ABSENCE of an assertion
38
+ // wearing assertion syntax, which is why they are the ones worth a debt list.
39
+ const fixture = [
40
+ 'test("a", () => {',
41
+ ' expect(true).toBe(true);',
42
+ ' try { go(); } catch {}',
43
+ '});',
44
+ 'test("b", () => {',
45
+ ' expect(x).toBe(1);',
46
+ '});',
47
+ ].join('\n');
48
+ const findings = scanForAssertionDebt({ source: fixture, file: 'fixture.test.ts' });
49
+ const flat = findings.flatMap((f) => f.findings);
50
+ expect(flat.some((f) => f.kind === 'tautological')).toBe(true);
51
+ expect(flat.some((f) => f.kind === 'swallowed-error')).toBe(true);
52
+ // A real assertion must not be reported -- a debt list that flags `expect(x).toBe(1)`
53
+ // is a list nobody finishes.
54
+ const realLine = flat.find((f) => f.text.includes('expect(x).toBe(1)'));
55
+ expect(realLine).toBeUndefined();
56
+ });
57
+
58
+ it('AC-668-03: 摘要给出可批次清零的数量,而非只是行数', () => {
59
+ const fixture = ['expect(true).toBe(true);', 'expect(1).toBe(1);', 'try { x(); } catch {}'].join('\n');
60
+ const results = scanForAssertionDebt({ source: fixture, file: 'a.test.ts' });
61
+ const summary = summarizeDebt(results);
62
+ expect(summary.files).toBe(1);
63
+ expect(summary.total).toBe(3);
64
+ // Two tautologies and one empty catch: the split matters because they are fixed
65
+ // by different edits (rewrite the assertion vs. handle the error).
66
+ expect(summary.byKind.tautological).toBe(2);
67
+ expect(summary.byKind['swallowed-error']).toBe(1);
68
+ });
69
+ });
70
+
71
+ /**
72
+ * @test REQ-668 fixture 不能被当作存量债务
73
+ * @intent 这条测试防的是一个**修债务反而删掉测试覆盖**的失败模式。
74
+ *
75
+ * 测试 Gate 5d 分类器的那批 .bats / .test.ts 文件必然包含
76
+ * `'expect(true).toBe(true);'` —— 那是被校验的**输入**,不是断言。
77
+ * 若它们被列进"去修掉"的清单,执行清单的人会删掉钉住分类器行为的字符串,
78
+ * 于是债务清单本身成了覆盖率的破坏者。
79
+ *
80
+ * 反例同样重要:真正的裸断言 `expect(true).toBe(true);` 必须仍然被报出来。
81
+ * 只做"少报"很容易,难的是**同时**不误报也不漏报。
82
+ * @covers AC-668-04
83
+ */
84
+ describe('fixture 与真实断言的区分 (#668)', () => {
85
+ it('AC-668-04: 带引号的 fixture 不报,裸断言照报', () => {
86
+ const { classifyLine } = require('../assertion-debt');
87
+ // Real code: must be reported.
88
+ expect(classifyLine('expect(true).toBe(true);')).toBe('tautological');
89
+ expect(classifyLine(' expect(1).toBe(1);')).toBe('tautological');
90
+ expect(classifyLine('try { x(); } catch {}')).toBe('swallowed-error');
91
+ // Fixture inputs: must not be reported.
92
+ expect(classifyLine("' expect(true).toBe(true);',")).toBeNull();
93
+ expect(classifyLine('const s = "expect(true).toBe(true)";')).toBeNull();
94
+ // Neither is debt.
95
+ expect(classifyLine('// expect(true).toBe(true);')).toBeNull();
96
+ expect(classifyLine('expect(x).toBe(1);')).toBeNull();
97
+ expect(classifyLine('expect(value).toBeTruthy();')).toBeNull();
98
+ });
99
+ });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @test REQ-652 BUILD 阶段的门禁清单必须从真相源生成,而非手抄
3
+ * @intent #652 的实证是:BUILD 阶段生成代码时,模型对 Boy Scout / CCN / jscpd /
4
+ * archlint 完全不可知——不是"知道却不遵守",是**根本不知道规则存在**。
5
+ * 后果是 27% 的门禁失败属于静态规则违规,即唯一 100% 可在生成前规避的类别。
6
+ *
7
+ * 补一页手写的阈值表能解决今天的问题,但会制造明天的问题:`.principlesrc`
8
+ * 里 `long-function` 是 50、`deep-nesting` 是 4,一旦有人调成 60/5,那一页
9
+ * 手抄的表就开始说谎。**一个说谎的自检清单比没有清单更糟**——它会让人按错的
10
+ * 规则写代码,然后在提交时被告知另一套。
11
+ *
12
+ * 所以这个清单必须从配置文件生成。真相源有几个就在几个里读,不复制。
13
+ * @covers AC-652-01, AC-652-02, AC-652-03
14
+ */
15
+ 'use strict';
16
+
17
+ const fs = require('node:fs');
18
+ const path = require('node:path');
19
+ const { collectGateRules } = require('../gate-rules');
20
+
21
+ const REPO = path.join(__dirname, '..', '..', '..', '..');
22
+
23
+ describe('BUILD 阶段门禁清单 (#652)', () => {
24
+ it('AC-652-01: 从 .principlesrc 读出真实阈值,不是写死的数字', () => {
25
+ const rules = collectGateRules({ cwd: REPO });
26
+ const longFn = rules.find((r) => r.id === 'clean-code.long-function');
27
+ const nesting = rules.find((r) => r.id === 'clean-code.deep-nesting');
28
+ expect(longFn).toBeDefined();
29
+ expect(nesting).toBeDefined();
30
+ // The value must come from the config file. Reading .principlesrc here keeps the
31
+ // test honest: if the config changes and the generator does not follow, this fails.
32
+ const cfg = JSON.parse(fs.readFileSync(path.join(REPO, '.principlesrc'), 'utf8'));
33
+ expect(longFn.threshold).toBe(cfg.rules['clean-code']['long-function'].threshold);
34
+ expect(nesting.threshold).toBe(cfg.rules['clean-code']['deep-nesting'].threshold);
35
+ });
36
+
37
+ it('AC-652-02: 覆盖会阻断提交的每一类规则,而不只是 principles', () => {
38
+ const rules = collectGateRules({ cwd: REPO });
39
+ const sources = new Set(rules.map((r) => r.source));
40
+ // #652 named exactly these four as absent from BUILD docs.
41
+ for (const required of ['principles', 'ccn', 'jscpd', 'boy-scout']) {
42
+ expect(sources).toContain(required);
43
+ }
44
+ });
45
+
46
+ it('AC-652-03: 生成结果可渲染成一份自检清单,含阈值与出处', () => {
47
+ const rules = collectGateRules({ cwd: REPO });
48
+ for (const rule of rules) {
49
+ expect(typeof rule.id).toBe('string');
50
+ expect(typeof rule.source).toBe('string');
51
+ // Without the evidence pointer, "the threshold is 50" cannot be checked or
52
+ // argued with -- it is indistinguishable from a guess.
53
+ expect(typeof rule.evidence).toBe('string');
54
+ expect(rule.evidence).not.toBe('');
55
+ expect(['block', 'warn', 'info']).toContain(rule.severity);
56
+ }
57
+ });
58
+ });
@@ -0,0 +1,173 @@
1
+ /**
2
+ * `xp-gate audit assertions` — make pre-existing assertion debt visible (#668).
3
+ *
4
+ * Gate 5d deliberately checks only the files in the diff: a full-repo scan on
5
+ * every commit is too slow. That has an expensive side effect. Pre-existing debt
6
+ * blocks unrelated changes — the author who fixed `writeReport()`'s error handling
7
+ * was stopped by four long-standing `expect(true).toBe(true)` lines in a file they
8
+ * had merely touched — and the block reads as "this change introduced a problem".
9
+ *
10
+ * The worst consequence is not that one false attribution. It is what it does to
11
+ * behaviour: noise in the attribution makes people reach for
12
+ * `@assertion-justified` instead of fixing the test. A gate that teaches people to
13
+ * annotate their way past it is worse than no gate. Hence a batch work queue.
14
+ *
15
+ * Scope: only the two shapes Gate 5d BLOCKS (tautological, empty catch). Weak-only
16
+ * assertions and mock echoes WARN there because each can be legitimate; putting them
17
+ * in a "debt to clear" list would invent work.
18
+ */
19
+ 'use strict';
20
+
21
+ const fs = require('node:fs');
22
+ const path = require('node:path');
23
+
24
+ /** Directories that never contain hand-written assertions worth auditing. */
25
+ const SKIP_DIRS = new Set([
26
+ 'node_modules',
27
+ '.git',
28
+ 'dist',
29
+ 'build',
30
+ 'coverage',
31
+ '.stryker-tmp',
32
+ '.agent-tmp',
33
+ // Build output mirrored from src/ — listing it again would double-count debt and
34
+ // send a batch fixer to a file that regenerates.
35
+ '.xp-gate',
36
+ ]);
37
+
38
+ /** Test-file patterns across the ecosystems XP-Gate supports. */
39
+ function isTestFile(name) {
40
+ return (
41
+ /\.(test|spec)\.[cm]?[jt]sx?$/.test(name) ||
42
+ /(^|\/)(__tests__)\//.test(name) ||
43
+ /\.test\.bats$/.test(name) ||
44
+ /^test_.*\.py$/.test(name) ||
45
+ /_test\.(go|rs|py)$/.test(name)
46
+ );
47
+ }
48
+
49
+ /**
50
+ * Tautological assertions: the assertion cannot fail.
51
+ *
52
+ * Mirrors `classify_tautological_assertions` in githooks/lib/assertion-quality.sh.
53
+ * The two implementations must agree, because a debt list that disagrees with the
54
+ * gate it exists to serve sends people to fix lines that are not blocking and miss
55
+ * the ones that are.
56
+ */
57
+ const TAUTOLOGY_PATTERNS = [
58
+ // (a) identical literal on both sides.
59
+ {
60
+ re: /expect\s*\(\s*(true|false|null|undefined|-?\d+|"[^"]*"|'[^']*')\s*\)\s*\.\s*to(?:Be|Equal|StrictEqual)\s*\(\s*\1\s*\)/,
61
+ },
62
+ // (b) a literal tested against the truthiness it already has.
63
+ { re: /expect\s*\(\s*(?:true|1|-1|"[^"]+"|'[^']+')\s*\)\s*\.\s*toBeTruthy\s*\(/ },
64
+ { re: /expect\s*\(\s*(?:false|0|null|undefined|""|'')\s*\)\s*\.\s*toBeFalsy\s*\(/ },
65
+ ];
66
+
67
+ /** An empty catch body: the failure it guards is discarded. */
68
+ const EMPTY_CATCH_RE = /catch\s*(?:\([^)]*\))?\s*\{\s*\}/;
69
+
70
+ /**
71
+ * Is this line a fixture rather than a real assertion?
72
+ *
73
+ * A `.bats` test that exercises the Gate 5d classifier necessarily contains
74
+ * `'expect(true).toBe(true);'` as a quoted INPUT. Reporting it as debt would send a
75
+ * batch fixer to delete the very strings that pin the classifier — turning the
76
+ * "fix debt" instruction into an instruction to remove test coverage.
77
+ *
78
+ * The distinguishing feature is quoting: in a fixture the shape is wrapped in
79
+ * quotes and usually sits inside a list/assignment, where a real assertion is bare
80
+ * executable code.
81
+ */
82
+ function isFixtureLine(trimmed) {
83
+ // A leading quote before the assertion shape: `'expect(...)...'` or "expect(...)".
84
+ if (/^['"`]/.test(trimmed)) return true;
85
+ // Assertion shape appearing inside a quoted string on the line.
86
+ if (/['"][^'"`]*expect\s*\(/.test(trimmed)) return true;
87
+ return false;
88
+ }
89
+
90
+ /** Classify one source line, or null when it is fine. */
91
+ function classifyLine(text) {
92
+ const trimmed = text.trim();
93
+ if (trimmed === '') return null;
94
+ // A commented-out assertion is not an assertion.
95
+ if (/^(\/\/|\*|\/\*|#)/.test(trimmed)) return null;
96
+ if (isFixtureLine(trimmed)) return null;
97
+ for (const { re } of TAUTOLOGY_PATTERNS) {
98
+ if (re.test(trimmed)) return 'tautological';
99
+ }
100
+ if (EMPTY_CATCH_RE.test(trimmed)) return 'swallowed-error';
101
+ return null;
102
+ }
103
+
104
+ /** Find debt in one file's text. */
105
+ function scanSource(text, file) {
106
+ const findings = [];
107
+ const lines = text.split(/\r?\n/);
108
+ for (let i = 0; i < lines.length; i += 1) {
109
+ const kind = classifyLine(lines[i]);
110
+ if (kind) findings.push({ line: i + 1, kind, text: lines[i].trim() });
111
+ }
112
+ return findings.length > 0 ? [{ file, findings }] : [];
113
+ }
114
+
115
+ /** Walk the tree collecting test files, bounded to avoid node_modules. */
116
+ function listTestFiles(root, out = []) {
117
+ let entries;
118
+ try {
119
+ entries = fs.readdirSync(root, { withFileTypes: true });
120
+ } catch {
121
+ return out;
122
+ }
123
+ if (!Array.isArray(entries)) return out;
124
+ for (const entry of entries) {
125
+ if (entry.isDirectory()) {
126
+ if (SKIP_DIRS.has(entry.name)) continue;
127
+ listTestFiles(path.join(root, entry.name), out);
128
+ continue;
129
+ }
130
+ if (isTestFile(entry.name)) out.push(path.join(root, entry.name));
131
+ }
132
+ return out;
133
+ }
134
+
135
+ /**
136
+ * Scan for assertion debt.
137
+ *
138
+ * @param {{cwd?: string, source?: string, file?: string}} opts
139
+ * @returns {Array<{file: string, findings: Array<{line: number, kind: string, text: string}>}>}
140
+ */
141
+ function scanForAssertionDebt(opts = {}) {
142
+ if (typeof opts.source === 'string') {
143
+ return scanSource(opts.source, opts.file || '<inline>');
144
+ }
145
+ const root = opts.cwd || process.cwd();
146
+ const results = [];
147
+ for (const abs of listTestFiles(root)) {
148
+ let text;
149
+ try {
150
+ text = fs.readFileSync(abs, 'utf8');
151
+ } catch {
152
+ continue;
153
+ }
154
+ const rel = path.relative(root, abs).split(path.sep).join('/');
155
+ results.push(...scanSource(text, rel));
156
+ }
157
+ return results.sort((a, b) => b.findings.length - a.findings.length || a.file.localeCompare(b.file));
158
+ }
159
+
160
+ /** Totals for a debt run: per-kind counts plus the file count to batch over. */
161
+ function summarizeDebt(results) {
162
+ const byKind = { tautological: 0, 'swallowed-error': 0 };
163
+ let total = 0;
164
+ for (const entry of results) {
165
+ for (const finding of entry.findings) {
166
+ byKind[finding.kind] = (byKind[finding.kind] || 0) + 1;
167
+ total += 1;
168
+ }
169
+ }
170
+ return { files: results.length, total, byKind };
171
+ }
172
+
173
+ module.exports = { classifyLine, scanForAssertionDebt, summarizeDebt, isTestFile };
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Collect the gate thresholds that actually block a commit, from the files that
3
+ * define them (#652).
4
+ *
5
+ * Why generated rather than hand-written: BUILD-phase docs used to say "there are
6
+ * gates 0-11" without saying what the numbers are. The numbers live in
7
+ * `.principlesrc`, `jscpd.conf.json`, `.ccn-baseline.json` and the hook itself. A
8
+ * hand-copied table is correct on the day it is written and wrong on the day
9
+ * someone tunes a threshold — and a self-check list that lies is worse than none,
10
+ * because it teaches the wrong rule and then contradicts it at commit time.
11
+ *
12
+ * Every rule therefore carries an `evidence` pointer, so "the limit is 50" can be
13
+ * checked or argued with instead of being indistinguishable from a guess.
14
+ */
15
+ 'use strict';
16
+
17
+ const fs = require('node:fs');
18
+ const path = require('node:path');
19
+
20
+ /** Read and parse a JSON file, or null when absent/unreadable. */
21
+ function readJson(file) {
22
+ try {
23
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Normalise severity spellings.
31
+ *
32
+ * The config files and the hook do not agree: `.principlesrc` says `warning`,
33
+ * Gate 3's boundary is `warn`, and both mean the same thing. Carrying two spellings
34
+ * into one list would make it look like two different severities exist.
35
+ */
36
+ function normaliseSeverity(value) {
37
+ if (value === 'warning') return 'warn';
38
+ if (value === 'block' || value === 'warn' || value === 'info') return value;
39
+ return 'warn';
40
+ }
41
+
42
+ /** Flatten `.principlesrc` rules into entries with source + evidence. */
43
+ function principlesRules(cwd) {
44
+ const file = path.join(cwd, '.principlesrc');
45
+ const cfg = readJson(file);
46
+ if (!cfg || !cfg.rules) return [];
47
+ const out = [];
48
+ for (const group of Object.keys(cfg.rules)) {
49
+ for (const [name, rule] of Object.entries(cfg.rules[group] || {})) {
50
+ out.push({
51
+ id: `${group}.${name}`,
52
+ source: 'principles',
53
+ severity: normaliseSeverity(rule.severity),
54
+ // Not every rule is threshold-shaped (e.g. unused-imports); report the
55
+ // one that exists rather than inventing a 0.
56
+ threshold: typeof rule.threshold === 'number' ? rule.threshold : null,
57
+ evidence: '.principlesrc',
58
+ });
59
+ }
60
+ }
61
+ return out;
62
+ }
63
+
64
+ /**
65
+ * Gate 2's duplicate-code rule.
66
+ *
67
+ * `githooks/pre-commit` prints "jscpd found duplicated code (warning, not blocking
68
+ * by default)" -- this gate reports, it does not block. The BUILD-phase doc used to
69
+ * claim "jscpd similarity >5% BLOCK", which sent people to refactor for a verdict
70
+ * that never fires. A generated list must record what the hook does, not what the
71
+ * old prose said.
72
+ */
73
+ function jscpdRules(cwd) {
74
+ const cfg = readJson(path.join(cwd, 'jscpd.conf.json'));
75
+ // jscpd itself has no similarity threshold in this project's config and the hook
76
+ // never compares a percentage. Reporting null says "no limit declared" instead of
77
+ // inventing a number the hook does not enforce.
78
+ const threshold = cfg && typeof cfg.threshold === 'number' ? cfg.threshold : null;
79
+ return [
80
+ {
81
+ id: 'duplication.jscpd',
82
+ source: 'jscpd',
83
+ severity: 'info',
84
+ threshold,
85
+ evidence: 'githooks/pre-commit (Gate 2, advisory)',
86
+ },
87
+ ];
88
+ }
89
+
90
+ /** Gate 3 warn/block boundaries, which the hook itself owns. */
91
+ function ccnRules(cwd) {
92
+ return [
93
+ { id: 'ccn.warn', source: 'ccn', severity: 'warn', threshold: 5, evidence: 'githooks/pre-commit (Gate 3)' },
94
+ { id: 'ccn.block', source: 'ccn', severity: 'block', threshold: 10, evidence: 'githooks/pre-commit (Gate 3)' },
95
+ ];
96
+ }
97
+
98
+ /** Boy Scout's structural limits, read from wherever they are configured. */
99
+ function boyScoutRules(cwd) {
100
+ const out = [
101
+ {
102
+ id: 'boy-scout.new-file',
103
+ source: 'boy-scout',
104
+ severity: 'block',
105
+ threshold: 0,
106
+ evidence: 'src/principles/boy-scout.ts',
107
+ },
108
+ ];
109
+ // The per-function line and nesting limits are principles rules; when
110
+ // .principlesrc exists they are already listed by principlesRules, so only add
111
+ // here when it does not (so the BUILD list never silently loses them).
112
+ const cfg = readJson(path.join(cwd, '.principlesrc'));
113
+ if (!cfg || !cfg.rules || !cfg.rules['clean-code']) {
114
+ out.push(
115
+ { id: 'clean-code.long-function', source: 'boy-scout', severity: 'warn', threshold: 50, evidence: 'defaults' },
116
+ { id: 'clean-code.deep-nesting', source: 'boy-scout', severity: 'warn', threshold: 4, evidence: 'defaults' },
117
+ );
118
+ }
119
+ return out;
120
+ }
121
+
122
+ /**
123
+ * Collect every rule that can block or warn a commit.
124
+ *
125
+ * @param {{cwd?: string}} opts
126
+ * @returns {Array<{id: string, source: string, severity: string, threshold: number|null, evidence: string}>}
127
+ */
128
+ function collectGateRules(opts = {}) {
129
+ const cwd = opts.cwd || process.cwd();
130
+ const rules = [...principlesRules(cwd), ...jscpdRules(cwd), ...ccnRules(cwd), ...boyScoutRules(cwd)];
131
+ // Stable order: blocking first, then warn/info, so a reader stops at what hurts.
132
+ const rank = { block: 0, warn: 1, info: 2 };
133
+ return rules.sort(
134
+ (a, b) => (rank[a.severity] ?? 3) - (rank[b.severity] ?? 3) || a.id.localeCompare(b.id),
135
+ );
136
+ }
137
+
138
+ /** Render the checklist as markdown with an evidence column. */
139
+ function renderGateRules(rules) {
140
+ const lines = ['| Rule | Severity | Limit | Evidence |', '|---|---|---|---|'];
141
+ for (const rule of rules) {
142
+ lines.push(
143
+ `| \`${rule.id}\` | ${rule.severity} | ${rule.threshold === null ? '—' : rule.threshold} | \`${rule.evidence}\` |`,
144
+ );
145
+ }
146
+ return lines.join('\n');
147
+ }
148
+
149
+ module.exports = { collectGateRules, renderGateRules };
@@ -2,6 +2,6 @@
2
2
  "verdict": "PASS",
3
3
  "license_type": "MIT",
4
4
  "sha256": "0e7ac423bf2c6e223b7c5b156f8cf72da49d748e56a1641402c31f22ad07dbb5",
5
- "checked_at": "2026-10-30T18:23:41.600Z",
5
+ "checked_at": "2026-10-30T18:41:08.730Z",
6
6
  "source": "network"
7
7
  }
@@ -1,9 +1,9 @@
1
1
  # SRC/MOCK-POLICY KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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.
@@ -1,9 +1,9 @@
1
1
  # SRC/MUTATION KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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": "@boyingliu01/xp-gate",
3
- "version": "0.35.6",
3
+ "version": "0.35.7",
4
4
  "description": "AI-driven development workflow: 6 quality gates + Delphi review + Sprint Flow",
5
5
  "bin": {
6
6
  "xp-gate": "bin/xp-gate.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.35.6",
3
+ "version": "0.35.7",
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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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 相似度 **>5% BLOCK** | 抽公共函数,别复制粘贴;`xp-gate check 2 <dir>` |
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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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": "@boyingliu01/dsh-plugin-xp-gate",
3
- "version": "0.35.6",
3
+ "version": "0.35.7",
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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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 相似度 **>5% BLOCK** | 抽公共函数,别复制粘贴;`xp-gate check 2 <dir>` |
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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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": "@boyingliu01/opencode-plugin",
3
- "version": "0.35.6",
3
+ "version": "0.35.7",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "description": "XP-Gate quality gates + AI workflow skills + Sprint Flow TUI sidebar for OpenCode",
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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 相似度 **>5% BLOCK** | 抽公共函数,别复制粘贴;`xp-gate check 2 <dir>` |
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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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.6",
3
+ "version": "0.35.7",
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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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
  # PRINCIPLES CHECKER MODULE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.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 相似度 **>5% BLOCK** | 抽公共函数,别复制粘贴;`xp-gate check 2 <dir>` |
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:** 35df3831
4
+ **Commit:** 9f8b24d8
5
5
  **Branch:** main
6
- **Version:** 0.35.6.0
6
+ **Version:** 0.35.7.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.