@boyingliu01/xp-gate 0.35.7 → 0.35.9
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/hooks/pre-commit +77 -13
- package/lib/__tests__/debt-classify.test.js +78 -0
- package/lib/__tests__/mirror-manifest.test.js +64 -0
- package/lib/debt-classify.js +86 -0
- package/lib/gate-coverage.js +83 -9
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/mirror-manifest.js +112 -0
- 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/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/hooks/pre-commit
CHANGED
|
@@ -342,7 +342,9 @@ file_exists_in_subdir() {
|
|
|
342
342
|
# in every gate, so their definitions lead the first call.
|
|
343
343
|
# ============================================================================
|
|
344
344
|
AUDIT_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
345
|
-
|
|
345
|
+
# PROJECT_ROOT is resolved once above (line ~235); re-running `git rev-parse` here
|
|
346
|
+
# costs another ~90ms fork on every commit for the same answer (#640).
|
|
347
|
+
PROJECT_ROOT_FOR_AUDIT="${PROJECT_ROOT:-$(cd "$AUDIT_SCRIPT_DIR/../.." 2>/dev/null && pwd)}"
|
|
346
348
|
|
|
347
349
|
# Audit attribution safety net: if a previous commit's post-commit never fired
|
|
348
350
|
# (hooks skipped, crash, or an earlier --no-verify), its staged entries are
|
|
@@ -759,6 +761,10 @@ if [ "$is_protected" = "true" ]; then
|
|
|
759
761
|
else
|
|
760
762
|
# Check if staged changes include VERSION or CHANGELOG.md
|
|
761
763
|
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
|
|
764
|
+
# #640: a fork costs ~90ms on Windows (measured: 100 `git rev-parse` = 9456ms),
|
|
765
|
+
# and this exact query is re-run by later gates. Cache it once; consumers below
|
|
766
|
+
# read $XP_STAGED_ACM. Subprocess COUNT, not scan duration, is the bottleneck.
|
|
767
|
+
XP_STAGED_ACM="$STAGED_FILES"
|
|
762
768
|
for file in $STAGED_FILES; do
|
|
763
769
|
case "$file" in
|
|
764
770
|
VERSION) has_version_change=true ;;
|
|
@@ -918,7 +924,7 @@ echo ""
|
|
|
918
924
|
# scope rationale as the mirror exclusion (#507 Delphi round-2 follow-up).
|
|
919
925
|
MIRROR_PATH_FILTER='^src/npm-package/'
|
|
920
926
|
DASHBOARD_PATH_FILTER='^dashboard/'
|
|
921
|
-
RAW_CHANGED_FILES
|
|
927
|
+
RAW_CHANGED_FILES="${XP_STAGED_ACM:-$(git diff --cached --name-only --diff-filter=ACM)}"
|
|
922
928
|
|
|
923
929
|
# Dependencies and build output are not project code (#649).
|
|
924
930
|
#
|
|
@@ -2345,7 +2351,7 @@ else
|
|
|
2345
2351
|
GATE_5_SKIP_COUNT=0
|
|
2346
2352
|
GATE_5_DELETED_TESTS=""
|
|
2347
2353
|
if declare -f classify_test_focus >/dev/null 2>&1; then
|
|
2348
|
-
STAGED_TEST_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null \
|
|
2354
|
+
STAGED_TEST_FILES=$(printf '%s\n' "${XP_STAGED_ACM:-$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null)}" \
|
|
2349
2355
|
| grep -E '\.(test|spec)\.(ts|tsx|js|jsx|mjs|cjs|py|go|java|kt|cs|scala|ps1)$|(^|/)(test_[^/]*\.py|[^/]*_test\.(py|go))$' || true)
|
|
2350
2356
|
if [ -n "$STAGED_TEST_FILES" ]; then
|
|
2351
2357
|
while IFS= read -r _tf; do
|
|
@@ -2751,7 +2757,7 @@ else
|
|
|
2751
2757
|
# Gate 5b: Mock Density ADVISORY Scan (pure bash — no npx tsx overhead)
|
|
2752
2758
|
# 30% = ADVISORY, 50% = suggests @mock-justified. Does NOT block commit.
|
|
2753
2759
|
# ========================================================================
|
|
2754
|
-
CHANGED_TEST_FILES=$(git diff --cached --name-only --diff-filter=ACM | filter_project_paths | grep -E '\.(test|spec)\.(ts|tsx|js|jsx|cjs|mjs|py|go)$' || true)
|
|
2760
|
+
CHANGED_TEST_FILES=$(printf '%s\n' "${XP_STAGED_ACM:-$(git diff --cached --name-only --diff-filter=ACM)}" | filter_project_paths | grep -E '\.(test|spec)\.(ts|tsx|js|jsx|cjs|mjs|py|go)$' || true)
|
|
2755
2761
|
|
|
2756
2762
|
# Nested plugin sub-projects (plugins/<name>/) own their own vitest config and
|
|
2757
2763
|
# are excluded from the root suite (`vitest.config.ts` excludes `plugins/**`,
|
|
@@ -2899,7 +2905,7 @@ else
|
|
|
2899
2905
|
CHANGED_TEST_FILE_COUNT=$(echo "$SELECTED_TEST_FILES" | grep -c '.' || true)
|
|
2900
2906
|
|
|
2901
2907
|
# Also get changed source files (non-test) for related-test lookup
|
|
2902
|
-
CHANGED_SRC_FILES=$(git diff --cached --name-only --diff-filter=ACM | filter_project_paths | grep -E '\.(ts|tsx|js|jsx)$' | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '__tests__' | grep -v '\.d\.ts$' | grep -v '__snapshots__' || true)
|
|
2908
|
+
CHANGED_SRC_FILES=$(printf '%s\n' "${XP_STAGED_ACM:-$(git diff --cached --name-only --diff-filter=ACM)}" | filter_project_paths | grep -E '\.(ts|tsx|js|jsx)$' | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '__tests__' | grep -v '\.d\.ts$' | grep -v '__snapshots__' || true)
|
|
2903
2909
|
# Nested plugin sub-projects are excluded from the root suite; their
|
|
2904
2910
|
# sources must not drive root-vitest related-test lookup either (it
|
|
2905
2911
|
# would resolve to plugin-local paths vitest cannot collect).
|
|
@@ -3134,16 +3140,16 @@ else
|
|
|
3134
3140
|
# partial so Stage 2 warns instead of falsely blocking.
|
|
3135
3141
|
# full -> unchanged behavior: whole suite + real threshold.
|
|
3136
3142
|
# skip -> nothing test-relevant changed; no pytest at all.
|
|
3137
|
-
PY_CHANGED_TEST_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null \
|
|
3143
|
+
PY_CHANGED_TEST_FILES=$(printf '%s\n' "${XP_STAGED_ACM:-$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null)}" \
|
|
3138
3144
|
| filter_project_paths \
|
|
3139
3145
|
| grep -E '(^|/)test_[^/]*\.py$|_test\.py$' || true)
|
|
3140
|
-
PY_CHANGED_SRC_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null \
|
|
3146
|
+
PY_CHANGED_SRC_FILES=$(printf '%s\n' "${XP_STAGED_ACM:-$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null)}" \
|
|
3141
3147
|
| filter_project_paths \
|
|
3142
3148
|
| grep -E '\.py$' | grep -vE '(^|/)test_[^/]*\.py$|_test\.py$' || true)
|
|
3143
3149
|
# NOT filtered on purpose: this list answers "did a requirements
|
|
3144
3150
|
# file change", which is a property of the whole commit including
|
|
3145
3151
|
# paths under a vendor/ tree that filter_project_paths removes.
|
|
3146
|
-
PY_CHANGED_ALL
|
|
3152
|
+
PY_CHANGED_ALL="${XP_STAGED_ACM:-$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true)}"
|
|
3147
3153
|
# `grep -c '.'` prints its own zero AND exits 1 on an empty list
|
|
3148
3154
|
# (#493), so the count is derived with `|| true` + wc -l instead.
|
|
3149
3155
|
PY_TEST_COUNT=$(printf '%s\n' "$PY_CHANGED_TEST_FILES" | grep -c '.' || true)
|
|
@@ -3890,14 +3896,21 @@ else
|
|
|
3890
3896
|
exit 1
|
|
3891
3897
|
fi
|
|
3892
3898
|
echo " Running archlint for TypeScript..."
|
|
3893
|
-
#
|
|
3894
|
-
#
|
|
3895
|
-
|
|
3899
|
+
# #640: archlint costs ~8s and scans the whole tree, but an architecture
|
|
3900
|
+
# violation cannot be introduced by a commit that touches no source file.
|
|
3901
|
+
# Gating on "did any source change" removes the single largest fixed cost
|
|
3902
|
+
# from docs/config-only commits without weakening the check at all.
|
|
3903
|
+
if ! printf '%s\n' "${XP_STAGED_ACM:-}" | grep -qE '\.(ts|tsx|js|jsx|mjs|cjs|py|go|java|kt|kts|rs|cpp|cc|cxx|c|h|hpp|swift|dart)$'; then
|
|
3904
|
+
echo " ⏭️ SKIPPED - Architecture validation (no source files changed)"
|
|
3905
|
+
ARCHLINT_OUTPUT=""
|
|
3906
|
+
ARCHLINT_EXIT=0
|
|
3907
|
+
elif [ -f "$PROJECT_ROOT/.architecture-baseline.json" ]; then
|
|
3896
3908
|
ARCHLINT_OUTPUT=$(run_archlint diff .architecture-baseline.json --fail-on high 2>&1)
|
|
3909
|
+
ARCHLINT_EXIT=$?
|
|
3897
3910
|
else
|
|
3898
3911
|
ARCHLINT_OUTPUT=$(run_archlint scan . -f table --quiet 2>&1)
|
|
3912
|
+
ARCHLINT_EXIT=$?
|
|
3899
3913
|
fi
|
|
3900
|
-
ARCHLINT_EXIT=$?
|
|
3901
3914
|
echo "$ARCHLINT_OUTPUT" | tail -30
|
|
3902
3915
|
if [ "$ARCHLINT_EXIT" -ne 0 ]; then
|
|
3903
3916
|
echo ""
|
|
@@ -4606,7 +4619,58 @@ fi
|
|
|
4606
4619
|
# ============================================================================
|
|
4607
4620
|
|
|
4608
4621
|
# ============================================================================
|
|
4609
|
-
# GATE
|
|
4622
|
+
# GATE 12b: Mirror Parity (#654)
|
|
4623
|
+
#
|
|
4624
|
+
# `githooks/**` and `plugins/*/src/**` are canonical; `src/npm-package/**` holds
|
|
4625
|
+
# byte-identical mirrors that get published. Editing only the mirror means the next
|
|
4626
|
+
# prepack silently regenerates it over the change -- v0.27.3 shipped exactly that
|
|
4627
|
+
# bug, and mirror/sync rework accounts for 58 failures in the session logs.
|
|
4628
|
+
#
|
|
4629
|
+
# The check used to run only at pre-push, which turns a two-second fix into a
|
|
4630
|
+
# five-minute wait. It runs here now, but ONLY over files in this commit: a
|
|
4631
|
+
# full-tree scan on every commit is the cost that pushed it to pre-push in the
|
|
4632
|
+
# first place.
|
|
4633
|
+
# ============================================================================
|
|
4634
|
+
MIRROR_STAGED=$(git diff --cached --name-only --diff-filter=ACMR 2>/dev/null || true)
|
|
4635
|
+
if [ -n "$MIRROR_STAGED" ] && printf '%s\n' "$MIRROR_STAGED" | grep -qE '^(githooks/|plugins/[^/]+/src/)'; then
|
|
4636
|
+
# Reuse PROJECT_ROOT: `git rev-parse` costs ~90ms per fork on Windows and this
|
|
4637
|
+
# gate must not add two of them to every commit (#640).
|
|
4638
|
+
MIRROR_REPO_ROOT="${PROJECT_ROOT:-}"
|
|
4639
|
+
if [ -n "$MIRROR_REPO_ROOT" ] && [ -f "$MIRROR_REPO_ROOT/scripts/check-hook-mirror.sh" ]; then
|
|
4640
|
+
# Scope to the touched families only: a mirror pair is clean or dirty
|
|
4641
|
+
# independently, and checking untouched families is the slow path this gate
|
|
4642
|
+
# exists to avoid.
|
|
4643
|
+
MIRROR_FAIL=0
|
|
4644
|
+
while IFS= read -r _mf; do
|
|
4645
|
+
[ -n "$_mf" ] || continue
|
|
4646
|
+
case "$_mf" in
|
|
4647
|
+
githooks/*) _mm="src/npm-package/hooks/${_mf#githooks/}" ;;
|
|
4648
|
+
plugins/*/src/*) _mm=$(printf '%s' "$_mf" | sed 's|^plugins/\([^/]*\)/src/|src/npm-package/plugins/\1/src/|') ;;
|
|
4649
|
+
*) continue ;;
|
|
4650
|
+
esac
|
|
4651
|
+
if [ ! -f "$MIRROR_REPO_ROOT/$_mm" ]; then
|
|
4652
|
+
echo "❌ BLOCKED - Gate 12b: $_mf has no mirror at $_mm"
|
|
4653
|
+
echo " Run: node src/npm-package/scripts/sync-package-content.js --fix"
|
|
4654
|
+
MIRROR_FAIL=$((MIRROR_FAIL + 1))
|
|
4655
|
+
elif ! cmp -s "$MIRROR_REPO_ROOT/$_mf" "$MIRROR_REPO_ROOT/$_mm"; then
|
|
4656
|
+
echo "❌ BLOCKED - Gate 12b: $_mm differs from canonical $_mf"
|
|
4657
|
+
echo " Run: node src/npm-package/scripts/sync-package-content.js --fix"
|
|
4658
|
+
MIRROR_FAIL=$((MIRROR_FAIL + 1))
|
|
4659
|
+
fi
|
|
4660
|
+
done <<< "$MIRROR_STAGED"
|
|
4661
|
+
if [ "$MIRROR_FAIL" -gt 0 ]; then
|
|
4662
|
+
echo ""
|
|
4663
|
+
echo "❌ BLOCKED - Gate 12b: $MIRROR_FAIL mirror parity violation(s)."
|
|
4664
|
+
echo " The mirror is what gets published; a mirror-only fix is silently"
|
|
4665
|
+
echo " reverted by the next prepack (v0.27.3 shipped that bug)."
|
|
4666
|
+
GATE_FAILED=1
|
|
4667
|
+
else
|
|
4668
|
+
echo "✅ PASSED - Gate 12b: mirror parity (touched files)"
|
|
4669
|
+
fi
|
|
4670
|
+
fi
|
|
4671
|
+
fi
|
|
4672
|
+
# ============================================================================
|
|
4673
|
+
|
|
4610
4674
|
# The compiler is the first and cheapest static analyser for a compiled language
|
|
4611
4675
|
# and XP-Gate never used it as one: cpp.sh only runs clang-tidy/cppcheck over the
|
|
4612
4676
|
# first 20 files, and Gate 9's build-integrity half is TypeScript-and-Maven only.
|
|
@@ -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,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-654 镜像清单必须自证完整:漏登记的规范源文件要报错,而不是静默不同步
|
|
3
|
+
* @intent 会话日志里 `mirror/sync` 类失败 **58 次**,git 历史反复出现
|
|
4
|
+
* `fix(mirror): ...` / `fix(package): 把 java.sh 改动落到真正的源文件`。
|
|
5
|
+
*
|
|
6
|
+
* 根因不是模型不知道规则——`AGENTS.md` 写得很清楚——而是这类约束是
|
|
7
|
+
* **结构性信息**:哪个文件是源、哪个是镜像、谁依赖谁。模型即使知道规则,
|
|
8
|
+
* 也要在多文件编辑中持续追踪映射关系,而这正是工具该做的事。
|
|
9
|
+
*
|
|
10
|
+
* 提示词救不了的部分,必须靠**漏登记即报错**来兜底。静默不同步是最坏的形态:
|
|
11
|
+
* 它让一次疏忽变成一次发布回滚(v0.27.3 正是只改镜像导致发布回滚)。
|
|
12
|
+
* @covers AC-654-01, AC-654-02, AC-654-03
|
|
13
|
+
*/
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const { findUnlistedSources, declareSources } = require('../mirror-manifest');
|
|
19
|
+
|
|
20
|
+
const REPO = path.join(__dirname, '..', '..', '..', '..');
|
|
21
|
+
|
|
22
|
+
describe('镜像清单完整性 (#654)', () => {
|
|
23
|
+
it('AC-654-01: 列出 canonical 目录下所有应被镜像的文件', () => {
|
|
24
|
+
const declared = declareSources({ cwd: REPO });
|
|
25
|
+
expect(Array.isArray(declared)).toBe(true);
|
|
26
|
+
// A manifest that silently returns nothing would pass every other assertion here,
|
|
27
|
+
// so pin the shape: real file lists are non-trivial.
|
|
28
|
+
expect(declared.length).toBeGreaterThan(20);
|
|
29
|
+
for (const entry of declared) {
|
|
30
|
+
expect(typeof entry.source).toBe('string');
|
|
31
|
+
expect(typeof entry.mirror).toBe('string');
|
|
32
|
+
// Both paths are repo-relative, forward-slashed, so drift messages are readable.
|
|
33
|
+
expect(entry.source.includes('\\')).toBe(false);
|
|
34
|
+
expect(entry.mirror.includes('\\')).toBe(false);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('AC-654-02: 实际存在的规范源文件若未登记,必须被报出来', () => {
|
|
39
|
+
const unlisted = findUnlistedSources({ cwd: REPO });
|
|
40
|
+
// The invariant the issue asks for: zero unlisted sources. This is the assertion
|
|
41
|
+
// that turns "someone forgot to register java.sh" from a publish-time rollback
|
|
42
|
+
// into a red test at commit time.
|
|
43
|
+
expect(unlisted).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('AC-654-03: 合成一个未登记文件时能被检出(防止检测器本身失效)', () => {
|
|
47
|
+
// A detector that never fires is indistinguishable from "no drift". Prove it fires
|
|
48
|
+
// on a synthetic tree rather than trusting the empty result above.
|
|
49
|
+
const fixture = {
|
|
50
|
+
'githooks/pre-commit': 'x',
|
|
51
|
+
'githooks/lib/known.sh': 'x',
|
|
52
|
+
'githooks/lib/orphan.sh': 'x',
|
|
53
|
+
};
|
|
54
|
+
const unlisted = findUnlistedSources({
|
|
55
|
+
cwd: REPO,
|
|
56
|
+
files: Object.keys(fixture),
|
|
57
|
+
declared: [
|
|
58
|
+
{ source: 'githooks/pre-commit', mirror: 'src/npm-package/hooks/pre-commit' },
|
|
59
|
+
{ source: 'githooks/lib/known.sh', mirror: 'src/npm-package/hooks/lib/known.sh' },
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
expect(unlisted).toEqual(['githooks/lib/orphan.sh']);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -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,33 @@ 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
|
+
* Describe Boy Scout debt by how much of it is worth fixing (#638).
|
|
204
|
+
*
|
|
205
|
+
* `magic-numbers` is severity=info and accounts for roughly half the raw count, so a
|
|
206
|
+
* single total is un-actionable: clearing every real finding barely moves it, and a
|
|
207
|
+
* number that cannot express progress cannot direct work.
|
|
208
|
+
*/
|
|
209
|
+
function describeBoyScoutDebt(boyScout) {
|
|
210
|
+
const breakdown = boyScout.breakdown;
|
|
211
|
+
if (!breakdown) return `${boyScout.warnings} warning(s) / ${boyScout.files} file(s)`;
|
|
212
|
+
return `${breakdown.actionable} actionable / ${breakdown.informational} informational (${boyScout.files} file(s))`;
|
|
213
|
+
}
|
|
214
|
+
|
|
194
215
|
/** Render rows as the human-readable overview. */
|
|
195
216
|
function renderGateCoverage(rows, opts) {
|
|
196
217
|
const options = opts || {};
|
|
@@ -256,8 +277,61 @@ function readBoyScoutDebt(repoRoot) {
|
|
|
256
277
|
const value = baseline[key];
|
|
257
278
|
warnings += typeof value === 'number' ? value : (value && value.totalWarnings) || 0;
|
|
258
279
|
}
|
|
259
|
-
return { files: files.length, warnings };
|
|
280
|
+
return { files: files.length, warnings, breakdown: readDebtBreakdown(repoRoot, files) };
|
|
281
|
+
} catch {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Rule-level breakdown of the Boy Scout debt (#638).
|
|
288
|
+
*
|
|
289
|
+
* The baseline stores only `totalWarnings` per file, so the split cannot be derived
|
|
290
|
+
* from it. It also must not be recomputed on every `gate-coverage` call: analysing
|
|
291
|
+
* 17 files through the principles engine costs ~90s, and a report command that takes
|
|
292
|
+
* a minute and a half stops being run.
|
|
293
|
+
*
|
|
294
|
+
* Hence a cache keyed on the baseline file's mtime+size: any commit that changes the
|
|
295
|
+
* debt changes the baseline, which invalidates the cache. A stale split is worse
|
|
296
|
+
* than no split, because it would report progress that did not happen.
|
|
297
|
+
*/
|
|
298
|
+
function readDebtBreakdown(repoRoot, files) {
|
|
299
|
+
try {
|
|
300
|
+
const { summarizeDebtByFixability } = require('./debt-classify.js');
|
|
301
|
+
const baselinePath = path.join(repoRoot, '.warnings-baseline.json');
|
|
302
|
+
const stat = fs.statSync(baselinePath);
|
|
303
|
+
const cachePath = path.join(repoRoot, '.xp-gate', 'debt-breakdown.json');
|
|
304
|
+
const cacheKey = `${stat.mtimeMs}:${stat.size}`;
|
|
305
|
+
try {
|
|
306
|
+
const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
307
|
+
if (cached && cached.key === cacheKey && cached.breakdown) return cached.breakdown;
|
|
308
|
+
} catch {
|
|
309
|
+
// No usable cache -- fall through and recompute.
|
|
310
|
+
}
|
|
311
|
+
const { execFileSync } = require('node:child_process');
|
|
312
|
+
const targets = files.filter((f) => !f.includes('/src/npm-package/'));
|
|
313
|
+
const raw = execFileSync(
|
|
314
|
+
process.execPath,
|
|
315
|
+
[path.join(repoRoot, 'node_modules', 'tsx', 'dist', 'cli.mjs'),
|
|
316
|
+
path.join(repoRoot, 'src', 'principles', 'index.ts'),
|
|
317
|
+
'--files', targets.join(' '), '--format', 'json'],
|
|
318
|
+
{ cwd: repoRoot, encoding: 'utf8', timeout: 180000 },
|
|
319
|
+
);
|
|
320
|
+
const parsed = JSON.parse(raw);
|
|
321
|
+
const rows = Object.entries(parsed.ruleResults || {}).map(([rule, v]) => ({
|
|
322
|
+
rule,
|
|
323
|
+
count: v.violationCount || 0,
|
|
324
|
+
}));
|
|
325
|
+
const breakdown = summarizeDebtByFixability(rows);
|
|
326
|
+
try {
|
|
327
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
328
|
+
fs.writeFileSync(cachePath, JSON.stringify({ key: cacheKey, breakdown }));
|
|
329
|
+
} catch {
|
|
330
|
+
// A cache write failure must not break the report.
|
|
331
|
+
}
|
|
332
|
+
return breakdown;
|
|
260
333
|
} catch {
|
|
334
|
+
// Degrade to the plain total rather than failing the whole report.
|
|
261
335
|
return null;
|
|
262
336
|
}
|
|
263
337
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mirror manifest: which canonical sources must be byte-identical in the npm package
|
|
3
|
+
* (#654).
|
|
4
|
+
*
|
|
5
|
+
* `githooks/**` and `plugins/*/src/**` are canonical; `src/npm-package/**` holds
|
|
6
|
+
* byte-identical mirrors that get published. Editing a mirror only means the next
|
|
7
|
+
* prepack silently regenerates it — v0.27.3 shipped exactly that bug.
|
|
8
|
+
*
|
|
9
|
+
* `AGENTS.md` states the rule clearly and it still gets violated ~58 times, because
|
|
10
|
+
* the constraint is *structural* (which file is source, which is mirror, who depends
|
|
11
|
+
* on whom), not stylistic. Knowing the rule does not remove the need to track the
|
|
12
|
+
* mapping across a multi-file edit; that is tooling's job.
|
|
13
|
+
*
|
|
14
|
+
* Hence this module: the mapping is data, and a canonical file missing from the
|
|
15
|
+
* mapping is an error rather than a silent no-op.
|
|
16
|
+
*/
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const fs = require('node:fs');
|
|
20
|
+
const path = require('node:path');
|
|
21
|
+
|
|
22
|
+
/** Directories (repo-relative) whose files mirror into the package. */
|
|
23
|
+
const MIRRORED_ROOTS = [
|
|
24
|
+
{ from: 'githooks', to: 'src/npm-package/hooks' },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
/** Individual canonical files that mirror into a different location. */
|
|
28
|
+
const INDIVIDUAL_PAIRS = [
|
|
29
|
+
{ source: 'githooks/pre-commit', mirror: 'src/npm-package/hooks/pre-commit' },
|
|
30
|
+
{ source: 'githooks/pre-push', mirror: 'src/npm-package/hooks/pre-push' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/** Paths inside a mirrored root that are NOT mirrored (tests, docs). */
|
|
34
|
+
const EXCLUDED_PREFIXES = ['__tests__/', 'node_modules/', '.git/'];
|
|
35
|
+
|
|
36
|
+
function toPosix(p) {
|
|
37
|
+
return p.split(path.sep).join('/');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Recursively list files under `dir`, repo-relative and forward-slashed. */
|
|
41
|
+
function listFiles(root, dir, out = []) {
|
|
42
|
+
let entries;
|
|
43
|
+
try {
|
|
44
|
+
entries = fs.readdirSync(path.join(root, dir), { withFileTypes: true });
|
|
45
|
+
} catch {
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
if (!Array.isArray(entries)) return out;
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
const rel = toPosix(path.join(dir, entry.name));
|
|
51
|
+
if (entry.isDirectory()) {
|
|
52
|
+
if (EXCLUDED_PREFIXES.some((p) => `${entry.name}/` === p)) continue;
|
|
53
|
+
listFiles(root, rel, out);
|
|
54
|
+
} else {
|
|
55
|
+
out.push(rel);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Expand the declared mapping into concrete source→mirror pairs.
|
|
63
|
+
*
|
|
64
|
+
* @param {{cwd?: string}} opts
|
|
65
|
+
* @returns {Array<{source: string, mirror: string}>}
|
|
66
|
+
*/
|
|
67
|
+
function declareSources(opts = {}) {
|
|
68
|
+
const cwd = opts.cwd || process.cwd();
|
|
69
|
+
const pairs = [...INDIVIDUAL_PAIRS];
|
|
70
|
+
const seen = new Set(pairs.map((p) => p.source));
|
|
71
|
+
for (const root of MIRRORED_ROOTS) {
|
|
72
|
+
for (const file of listFiles(cwd, root.from)) {
|
|
73
|
+
if (seen.has(file)) continue;
|
|
74
|
+
const rest = file.slice(root.from.length + 1);
|
|
75
|
+
if (EXCLUDED_PREFIXES.some((prefix) => rest.startsWith(prefix))) continue;
|
|
76
|
+
pairs.push({ source: file, mirror: `${root.to}/${rest}` });
|
|
77
|
+
seen.add(file);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return pairs.sort((a, b) => a.source.localeCompare(b.source));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Canonical files that exist but appear in no mapping entry.
|
|
85
|
+
*
|
|
86
|
+
* This is the check that turns "someone forgot to register java.sh" from a
|
|
87
|
+
* publish-time rollback into a red test at commit time. Silence here is the failure
|
|
88
|
+
* mode worth preventing: a file that is never mirrored looks fine locally and
|
|
89
|
+
* disappears from the published package.
|
|
90
|
+
*
|
|
91
|
+
* @param {{cwd?: string, files?: string[], declared?: Array<{source: string}>}} opts
|
|
92
|
+
* @returns {string[]}
|
|
93
|
+
*/
|
|
94
|
+
function findUnlistedSources(opts = {}) {
|
|
95
|
+
const cwd = opts.cwd || process.cwd();
|
|
96
|
+
const declared = opts.declared || declareSources({ cwd });
|
|
97
|
+
const declaredSet = new Set(declared.map((entry) => entry.source));
|
|
98
|
+
const present = opts.files || listFiles(cwd, 'githooks');
|
|
99
|
+
return present
|
|
100
|
+
.filter((file) => !EXCLUDED_PREFIXES.some((prefix) => file.slice('githooks/'.length).startsWith(prefix)))
|
|
101
|
+
.filter((file) => !declaredSet.has(file))
|
|
102
|
+
.sort();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Human-readable drift report; empty array means clean. */
|
|
106
|
+
function describeUnlisted(unlisted) {
|
|
107
|
+
return unlisted.map(
|
|
108
|
+
(file) => `${file} is a canonical source with no mirror entry — the published package will not carry it`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = { declareSources, findUnlistedSources, describeUnlisted, MIRRORED_ROOTS };
|
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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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.9",
|
|
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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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.9",
|
|
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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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.9",
|
|
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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.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:** 35b9ec60
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.9.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|