@boyingliu01/xp-gate 0.35.17 → 0.35.19
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/adapters/gate-3.sh +94 -9
- package/adapters/gate-4.sh +27 -32
- package/adapters/gate-8.sh +32 -3
- package/adapters/gate-9.sh +14 -2
- package/gate-12-file-hygiene.sh +26 -4
- package/gate-13-compiler-strictness.sh +12 -0
- package/gates/gate-3.ts +77 -21
- package/gates/gate-8.ts +39 -26
- package/gates/gate-9.ts +4 -4
- package/hooks/gate-12-file-hygiene.sh +26 -4
- package/hooks/gate-13-compiler-strictness.sh +12 -0
- package/hooks/pre-commit +75 -11
- package/hooks/pre-push +11 -2
- package/lib/__tests__/doctor-delphi.test.js +114 -0
- package/lib/__tests__/doctor-tui-skill-freshness.test.js +74 -0
- package/lib/__tests__/doctor.test.js +9 -0
- package/lib/__tests__/gate-numbering.test.js +52 -0
- package/lib/__tests__/gate-runner.test.js +59 -14
- package/lib/__tests__/shared-utils.test.js +47 -0
- package/lib/__tests__/update-skill.test.js +77 -0
- package/lib/doctor-tui.js +23 -2
- package/lib/doctor.js +28 -7
- package/lib/gate-runner.js +98 -18
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/shared-utils.js +27 -1
- package/lib/update-skill.js +59 -3
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/claude-code/skills/sprint-flow/__tests__/verify-sprint-state.test.bats +48 -0
- package/plugins/claude-code/skills/sprint-flow/references/definition-of-done.md +47 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-4-verify.md +16 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-5-ship.md +14 -0
- package/plugins/claude-code/skills/sprint-flow/scripts/verify-sprint-state.sh +14 -1
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/dsh/skills/sprint-flow/__tests__/verify-sprint-state.test.bats +48 -0
- package/plugins/dsh/skills/sprint-flow/references/definition-of-done.md +47 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-4-verify.md +16 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-5-ship.md +14 -0
- package/plugins/dsh/skills/sprint-flow/scripts/verify-sprint-state.sh +14 -1
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/dsh/src/delphi-findings.test.ts +32 -6
- package/plugins/dsh/src/exit-code.test.ts +6 -0
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/opencode/skills/sprint-flow/__tests__/verify-sprint-state.test.bats +48 -0
- package/plugins/opencode/skills/sprint-flow/references/definition-of-done.md +47 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-4-verify.md +16 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-5-ship.md +14 -0
- package/plugins/opencode/skills/sprint-flow/scripts/verify-sprint-state.sh +14 -1
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/qoder/skills/sprint-flow/__tests__/verify-sprint-state.test.bats +48 -0
- package/plugins/qoder/skills/sprint-flow/references/definition-of-done.md +47 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-4-verify.md +16 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-5-ship.md +14 -0
- package/plugins/qoder/skills/sprint-flow/scripts/verify-sprint-state.sh +14 -1
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/scripts/__tests__/sync-package-content.test.js +114 -46
- package/scripts/sync-package-content.js +32 -1
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/sprint-flow/__tests__/verify-sprint-state.test.bats +48 -0
- package/skills/sprint-flow/references/definition-of-done.md +47 -0
- package/skills/sprint-flow/references/phase-4-verify.md +16 -0
- package/skills/sprint-flow/references/phase-5-ship.md +14 -0
- package/skills/sprint-flow/scripts/verify-sprint-state.sh +14 -1
- package/skills/test-specification-alignment/AGENTS.md +3 -3
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Definition of Done(统一完成定义)
|
|
2
|
+
|
|
3
|
+
一个 REQ 只有在**全部 10 项**都可指向证据时才算完成。这份清单是 #607 的落地,
|
|
4
|
+
对应指南 §12;它存在的理由是本仓库历史上反复出现「以为完成了,其实没有」:
|
|
5
|
+
|
|
6
|
+
- **#561** 覆盖率阈值形状写错(`Path` 而非 `Partial<Record>`),声明 80% 但整个门禁是空转
|
|
7
|
+
- **#570** 覆盖率检查 fail-open,跑不起来时报告通过
|
|
8
|
+
- **#585** Gate 5c 静默通过
|
|
9
|
+
- **.xp-gate/modules 陈旧副本**(2026-10-19)劫持 Gate 6 数周,hook 报的警告数和独立扫描永远对不上
|
|
10
|
+
|
|
11
|
+
共同点全部是:**某一步从未真正执行,而没有任何东西把这件事说出来。**
|
|
12
|
+
所以这份清单里每一项的判据都是「证据在哪儿」,不是「做没做」。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 10 项完成定义
|
|
17
|
+
|
|
18
|
+
| # | 条目 | 判据(必须能指出位置) |
|
|
19
|
+
|---|------|------------------------|
|
|
20
|
+
| 1 | **需求可追溯** | `specification.yaml` 有 REQ + AC;测试文件带 `@covers AC-xxx`;`scripts/__tests__/ac-reference-guard.test.cjs` 通过 |
|
|
21
|
+
| 2 | **测试先行** | 实现前存在失败的测试;或说明为何不适用(纯文档/配置) |
|
|
22
|
+
| 3 | **测试真的能失败** | 至少一个变异体/破坏被测试抓住。「测试通过」在从未运行时和「功能正确」无法区分 |
|
|
23
|
+
| 4 | **验证命令已运行** | 记录实际命令与输出,不是「应该是绿的」。CI 命令与本地命令一致 |
|
|
24
|
+
| 5 | **门禁未降级** | 无 `--no-verify`;无新增基线额度来吸收本次改动自身的告警 |
|
|
25
|
+
| 6 | **镜像已同步** | 改了 `githooks/`/`skills/`/`plugins/` → `bash scripts/check-hook-mirror.sh` 输出 PASS |
|
|
26
|
+
| 7 | **不做未经要求的相邻改动** | 每行改动可追溯到本 REQ;顺手发现的无关死代码只提及不删除 |
|
|
27
|
+
| 8 | **已知限制已写明** | 没做的部分、以及为什么没做,写进 issue/提交,而不是留空 |
|
|
28
|
+
| 9 | **文档与实现一致** | 门禁数量、CLI 表、阈值等出现在多处的事实同步更新(见 `AGENTS.md` "Known Drift") |
|
|
29
|
+
| 10 | **可回滚** | 提交粒度单一;出问题时能单独 revert 而不牵连其他改动 |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 相位落点
|
|
34
|
+
|
|
35
|
+
- **Phase 4 VERIFY**:逐项过 1/2/3/4/5/6,任何一项拿不出证据 → 不得进入 SHIP。
|
|
36
|
+
- **Phase 5 SHIP**:逐项过 7/8/9/10,然后才推送。
|
|
37
|
+
- **Phase 6 CLOSE**:把第 8 项「已知限制」回填到 issue 并关闭。
|
|
38
|
+
|
|
39
|
+
## 常见误判
|
|
40
|
+
|
|
41
|
+
| 看起来完成了 | 实际没有 |
|
|
42
|
+
|---|---|
|
|
43
|
+
| 「测试通过」 | 没跑过会失败的版本,不知道它能不能失败 |
|
|
44
|
+
| 「门禁有配置」 | 配置形状错/键名不对时被静默丢弃,等于没有(#561) |
|
|
45
|
+
| 「工具缺失时跳过」 | 跳过被当成通过;只有 Gate 8 是 fail-closed |
|
|
46
|
+
| 「CI 绿了」 | CI 可能没跑到那一步(`if:` 条件不满足),或跑的是旧副本(#451) |
|
|
47
|
+
| 「注释写了这么做」 | 注释与代码漂移;只有测试能固定行为 |
|
|
@@ -142,6 +142,22 @@ ELSE:
|
|
|
142
142
|
- Web 前端附加: ui-review report + OPTIONAL QA/design-review/benchmark reports
|
|
143
143
|
- 验证通过的 MVP
|
|
144
144
|
|
|
145
|
+
### 完成定义(DoD 第 1–6 项)
|
|
146
|
+
|
|
147
|
+
离开 VERIFY 前逐项过一遍 [`definition-of-done.md`](./definition-of-done.md)。**任何一项拿不出证据 → 不得进入 SHIP。**
|
|
148
|
+
|
|
149
|
+
| # | 条目 | 在 VERIFY 的检查方式 |
|
|
150
|
+
|---|------|----------------------|
|
|
151
|
+
| 1 | 需求可追溯 | `npx vitest run scripts/__tests__/ac-reference-guard.test.cjs` 通过(测试引用的 AC 都在 spec 里) |
|
|
152
|
+
| 2 | 测试先行 | 有 RED 证据(提交历史或记录),或说明为何不适用 |
|
|
153
|
+
| 3 | **测试真的能失败** | 至少证明过一个变异体会被抓住——「测试通过」在从未运行时与「功能正确」无法区分 |
|
|
154
|
+
| 4 | 验证命令已运行 | 记录实际命令 + 输出,不是「应该是绿的」 |
|
|
155
|
+
| 5 | 门禁未降级 | 无 `--no-verify`;无为了本次改动新增的基线额度 |
|
|
156
|
+
| 6 | 镜像已同步 | `bash scripts/check-hook-mirror.sh` → PASS |
|
|
157
|
+
|
|
158
|
+
第 5 项值得单独说明:**加基线额度来吸收自己刚产生的告警,等于把门禁关掉一次。**
|
|
159
|
+
`.warnings-baseline.json` 只应记录「改动之前就存在」的告警。
|
|
160
|
+
|
|
145
161
|
---
|
|
146
162
|
|
|
147
163
|
## Part B: FEEDBACK CAPTURE(反馈获取)
|
|
@@ -99,6 +99,20 @@ Sprint 分支已准备就绪。请选择操作:
|
|
|
99
99
|
|
|
100
100
|
### Step 2: 原生 Ship 步骤序列(选择 Option 2 时执行)
|
|
101
101
|
|
|
102
|
+
#### Step 2.0: 完成定义后半段(DoD 第 7–10 项,MANDATORY)
|
|
103
|
+
|
|
104
|
+
推送前逐项过 [`definition-of-done.md`](./definition-of-done.md):
|
|
105
|
+
|
|
106
|
+
| # | 条目 | 在这里怎么查 |
|
|
107
|
+
|---|------|--------------|
|
|
108
|
+
| 7 | 不做未经要求的相邻改动 | `git diff` 逐行对照 REQ;顺手发现的无关问题只提及,不删改 |
|
|
109
|
+
| 8 | **已知限制已写明** | 没做的部分与原因写进提交说明或 issue,而不是留空 |
|
|
110
|
+
| 9 | 文档与实现一致 | 门禁数量/CLI 表/阈值等出现在多处的事实同步更新(`AGENTS.md` "Known Drift") |
|
|
111
|
+
| 10 | 可回滚 | 提交粒度单一,能单独 revert 而不牵连其他改动 |
|
|
112
|
+
|
|
113
|
+
第 8 项最容易被跳过,也最容易造成误导:**一个没写下来的限制,下一个人会按"已完成"来依赖它。**
|
|
114
|
+
|
|
115
|
+
|
|
102
116
|
```
|
|
103
117
|
1. 运行测试:
|
|
104
118
|
npm test # 或项目特定测试命令
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
# Checks that sprint-state.json is valid JSON and all expected keys exist.
|
|
4
4
|
#
|
|
5
5
|
# Usage: bash verify-sprint-state.sh [sprint-state-dir]
|
|
6
|
+
#
|
|
7
|
+
# The expected keys are the ones `xp-gate sprint-init` actually writes (#679).
|
|
8
|
+
# They were previously ['sprint_id','status','current_phase','phases'] -- a schema
|
|
9
|
+
# this project has never written, so the check failed on every sprint the CLI
|
|
10
|
+
# itself had just created. A verifier that rejects its own producer's output is
|
|
11
|
+
# worse than no verifier: it trains people to ignore it.
|
|
6
12
|
|
|
7
13
|
set -euo pipefail
|
|
8
14
|
|
|
@@ -24,12 +30,19 @@ node -e "
|
|
|
24
30
|
const fs = require('fs');
|
|
25
31
|
try {
|
|
26
32
|
const data = JSON.parse(fs.readFileSync('$STATE_FILE', 'utf8'));
|
|
27
|
-
|
|
33
|
+
// These are the keys src/npm-package/lib/sprint-init.js writes.
|
|
34
|
+
const requiredKeys = ['id', 'task_description', 'phase', 'status'];
|
|
28
35
|
const missing = requiredKeys.filter(k => !(k in data));
|
|
29
36
|
if (missing.length > 0) {
|
|
30
37
|
console.error('ERROR: Missing required keys in sprint-state.json:', missing.join(', '));
|
|
31
38
|
process.exit(1);
|
|
32
39
|
}
|
|
40
|
+
// phase_history is an array by construction; a non-array here means the file
|
|
41
|
+
// was hand-edited or written by a different tool, which is worth failing on.
|
|
42
|
+
if (!Array.isArray(data.phase_history)) {
|
|
43
|
+
console.error('ERROR: phase_history must be an array in sprint-state.json');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
33
46
|
console.log('OK: sprint-state.json is valid with all required keys');
|
|
34
47
|
} catch (e) {
|
|
35
48
|
console.error('ERROR: sprint-state.json is not valid JSON:', e.message);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-11-01
|
|
4
|
+
**Commit:** effea31d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.19.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.19",
|
|
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
|
-
**Generated:** 2026-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-11-01
|
|
4
|
+
**Commit:** effea31d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.19.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
|
-
**Generated:** 2026-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-11-01
|
|
4
|
+
**Commit:** effea31d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.19.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.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
# @test REQ-688
|
|
3
|
+
# @intent 验证 sprint-flow 的校验脚本能接受 `xp-gate sprint-init` 自己写出的状态文件
|
|
4
|
+
# ——脚本曾要求 sprint_id/current_phase/phases 这套从未被写入过的键,
|
|
5
|
+
# 对 CLI 刚创建的 sprint 恒判失败(#679)
|
|
6
|
+
# @covers AC-688-01, AC-688-02
|
|
7
|
+
#
|
|
8
|
+
# 为什么用真 sprint-init 而不是手写一个 fixture:缺陷的本质是「校验方与生产方对
|
|
9
|
+
# schema 的认知不一致」。手写 fixture 会把「我认为 CLI 写什么」固化下来,双方一起
|
|
10
|
+
# 错就一起绿。只有让生产方真的产出、校验方真的读,才能发现这类偏差。
|
|
11
|
+
|
|
12
|
+
setup() {
|
|
13
|
+
REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../../.." && pwd)"
|
|
14
|
+
VERIFY="$REPO_ROOT/skills/sprint-flow/scripts/verify-sprint-state.sh"
|
|
15
|
+
CLI="$REPO_ROOT/src/npm-package/bin/xp-gate.js"
|
|
16
|
+
WORK="$(mktemp -d)"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
teardown() {
|
|
20
|
+
rm -rf "$WORK"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@test "AC-688-01: accepts state written by xp-gate sprint-init" {
|
|
24
|
+
cd "$WORK"
|
|
25
|
+
git init -q .
|
|
26
|
+
echo '{}' > package.json
|
|
27
|
+
git add -A
|
|
28
|
+
git -c user.email=t@t -c user.name=t commit -q -m init
|
|
29
|
+
|
|
30
|
+
run node "$CLI" sprint-init "probe task"
|
|
31
|
+
[ -f ".sprint-state/sprint-state.json" ]
|
|
32
|
+
|
|
33
|
+
run bash "$VERIFY"
|
|
34
|
+
[ "$status" -eq 0 ]
|
|
35
|
+
[[ "$output" == *"OK:"* ]]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@test "AC-688-02: still rejects a state file missing real keys" {
|
|
39
|
+
# The fix must not become "accept anything": a file that lost its task or its
|
|
40
|
+
# phase is genuinely broken and the verifier should say so.
|
|
41
|
+
mkdir -p "$WORK/.sprint-state"
|
|
42
|
+
printf '{"id":"s-1","phase":"BUILD"}\n' > "$WORK/.sprint-state/sprint-state.json"
|
|
43
|
+
|
|
44
|
+
cd "$WORK"
|
|
45
|
+
run bash "$VERIFY"
|
|
46
|
+
[ "$status" -eq 1 ]
|
|
47
|
+
[[ "$output" == *"task_description"* ]]
|
|
48
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Definition of Done(统一完成定义)
|
|
2
|
+
|
|
3
|
+
一个 REQ 只有在**全部 10 项**都可指向证据时才算完成。这份清单是 #607 的落地,
|
|
4
|
+
对应指南 §12;它存在的理由是本仓库历史上反复出现「以为完成了,其实没有」:
|
|
5
|
+
|
|
6
|
+
- **#561** 覆盖率阈值形状写错(`Path` 而非 `Partial<Record>`),声明 80% 但整个门禁是空转
|
|
7
|
+
- **#570** 覆盖率检查 fail-open,跑不起来时报告通过
|
|
8
|
+
- **#585** Gate 5c 静默通过
|
|
9
|
+
- **.xp-gate/modules 陈旧副本**(2026-10-19)劫持 Gate 6 数周,hook 报的警告数和独立扫描永远对不上
|
|
10
|
+
|
|
11
|
+
共同点全部是:**某一步从未真正执行,而没有任何东西把这件事说出来。**
|
|
12
|
+
所以这份清单里每一项的判据都是「证据在哪儿」,不是「做没做」。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 10 项完成定义
|
|
17
|
+
|
|
18
|
+
| # | 条目 | 判据(必须能指出位置) |
|
|
19
|
+
|---|------|------------------------|
|
|
20
|
+
| 1 | **需求可追溯** | `specification.yaml` 有 REQ + AC;测试文件带 `@covers AC-xxx`;`scripts/__tests__/ac-reference-guard.test.cjs` 通过 |
|
|
21
|
+
| 2 | **测试先行** | 实现前存在失败的测试;或说明为何不适用(纯文档/配置) |
|
|
22
|
+
| 3 | **测试真的能失败** | 至少一个变异体/破坏被测试抓住。「测试通过」在从未运行时和「功能正确」无法区分 |
|
|
23
|
+
| 4 | **验证命令已运行** | 记录实际命令与输出,不是「应该是绿的」。CI 命令与本地命令一致 |
|
|
24
|
+
| 5 | **门禁未降级** | 无 `--no-verify`;无新增基线额度来吸收本次改动自身的告警 |
|
|
25
|
+
| 6 | **镜像已同步** | 改了 `githooks/`/`skills/`/`plugins/` → `bash scripts/check-hook-mirror.sh` 输出 PASS |
|
|
26
|
+
| 7 | **不做未经要求的相邻改动** | 每行改动可追溯到本 REQ;顺手发现的无关死代码只提及不删除 |
|
|
27
|
+
| 8 | **已知限制已写明** | 没做的部分、以及为什么没做,写进 issue/提交,而不是留空 |
|
|
28
|
+
| 9 | **文档与实现一致** | 门禁数量、CLI 表、阈值等出现在多处的事实同步更新(见 `AGENTS.md` "Known Drift") |
|
|
29
|
+
| 10 | **可回滚** | 提交粒度单一;出问题时能单独 revert 而不牵连其他改动 |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 相位落点
|
|
34
|
+
|
|
35
|
+
- **Phase 4 VERIFY**:逐项过 1/2/3/4/5/6,任何一项拿不出证据 → 不得进入 SHIP。
|
|
36
|
+
- **Phase 5 SHIP**:逐项过 7/8/9/10,然后才推送。
|
|
37
|
+
- **Phase 6 CLOSE**:把第 8 项「已知限制」回填到 issue 并关闭。
|
|
38
|
+
|
|
39
|
+
## 常见误判
|
|
40
|
+
|
|
41
|
+
| 看起来完成了 | 实际没有 |
|
|
42
|
+
|---|---|
|
|
43
|
+
| 「测试通过」 | 没跑过会失败的版本,不知道它能不能失败 |
|
|
44
|
+
| 「门禁有配置」 | 配置形状错/键名不对时被静默丢弃,等于没有(#561) |
|
|
45
|
+
| 「工具缺失时跳过」 | 跳过被当成通过;只有 Gate 8 是 fail-closed |
|
|
46
|
+
| 「CI 绿了」 | CI 可能没跑到那一步(`if:` 条件不满足),或跑的是旧副本(#451) |
|
|
47
|
+
| 「注释写了这么做」 | 注释与代码漂移;只有测试能固定行为 |
|
|
@@ -142,6 +142,22 @@ ELSE:
|
|
|
142
142
|
- Web 前端附加: ui-review report + OPTIONAL QA/design-review/benchmark reports
|
|
143
143
|
- 验证通过的 MVP
|
|
144
144
|
|
|
145
|
+
### 完成定义(DoD 第 1–6 项)
|
|
146
|
+
|
|
147
|
+
离开 VERIFY 前逐项过一遍 [`definition-of-done.md`](./definition-of-done.md)。**任何一项拿不出证据 → 不得进入 SHIP。**
|
|
148
|
+
|
|
149
|
+
| # | 条目 | 在 VERIFY 的检查方式 |
|
|
150
|
+
|---|------|----------------------|
|
|
151
|
+
| 1 | 需求可追溯 | `npx vitest run scripts/__tests__/ac-reference-guard.test.cjs` 通过(测试引用的 AC 都在 spec 里) |
|
|
152
|
+
| 2 | 测试先行 | 有 RED 证据(提交历史或记录),或说明为何不适用 |
|
|
153
|
+
| 3 | **测试真的能失败** | 至少证明过一个变异体会被抓住——「测试通过」在从未运行时与「功能正确」无法区分 |
|
|
154
|
+
| 4 | 验证命令已运行 | 记录实际命令 + 输出,不是「应该是绿的」 |
|
|
155
|
+
| 5 | 门禁未降级 | 无 `--no-verify`;无为了本次改动新增的基线额度 |
|
|
156
|
+
| 6 | 镜像已同步 | `bash scripts/check-hook-mirror.sh` → PASS |
|
|
157
|
+
|
|
158
|
+
第 5 项值得单独说明:**加基线额度来吸收自己刚产生的告警,等于把门禁关掉一次。**
|
|
159
|
+
`.warnings-baseline.json` 只应记录「改动之前就存在」的告警。
|
|
160
|
+
|
|
145
161
|
---
|
|
146
162
|
|
|
147
163
|
## Part B: FEEDBACK CAPTURE(反馈获取)
|
|
@@ -99,6 +99,20 @@ Sprint 分支已准备就绪。请选择操作:
|
|
|
99
99
|
|
|
100
100
|
### Step 2: 原生 Ship 步骤序列(选择 Option 2 时执行)
|
|
101
101
|
|
|
102
|
+
#### Step 2.0: 完成定义后半段(DoD 第 7–10 项,MANDATORY)
|
|
103
|
+
|
|
104
|
+
推送前逐项过 [`definition-of-done.md`](./definition-of-done.md):
|
|
105
|
+
|
|
106
|
+
| # | 条目 | 在这里怎么查 |
|
|
107
|
+
|---|------|--------------|
|
|
108
|
+
| 7 | 不做未经要求的相邻改动 | `git diff` 逐行对照 REQ;顺手发现的无关问题只提及,不删改 |
|
|
109
|
+
| 8 | **已知限制已写明** | 没做的部分与原因写进提交说明或 issue,而不是留空 |
|
|
110
|
+
| 9 | 文档与实现一致 | 门禁数量/CLI 表/阈值等出现在多处的事实同步更新(`AGENTS.md` "Known Drift") |
|
|
111
|
+
| 10 | 可回滚 | 提交粒度单一,能单独 revert 而不牵连其他改动 |
|
|
112
|
+
|
|
113
|
+
第 8 项最容易被跳过,也最容易造成误导:**一个没写下来的限制,下一个人会按"已完成"来依赖它。**
|
|
114
|
+
|
|
115
|
+
|
|
102
116
|
```
|
|
103
117
|
1. 运行测试:
|
|
104
118
|
npm test # 或项目特定测试命令
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
# Checks that sprint-state.json is valid JSON and all expected keys exist.
|
|
4
4
|
#
|
|
5
5
|
# Usage: bash verify-sprint-state.sh [sprint-state-dir]
|
|
6
|
+
#
|
|
7
|
+
# The expected keys are the ones `xp-gate sprint-init` actually writes (#679).
|
|
8
|
+
# They were previously ['sprint_id','status','current_phase','phases'] -- a schema
|
|
9
|
+
# this project has never written, so the check failed on every sprint the CLI
|
|
10
|
+
# itself had just created. A verifier that rejects its own producer's output is
|
|
11
|
+
# worse than no verifier: it trains people to ignore it.
|
|
6
12
|
|
|
7
13
|
set -euo pipefail
|
|
8
14
|
|
|
@@ -24,12 +30,19 @@ node -e "
|
|
|
24
30
|
const fs = require('fs');
|
|
25
31
|
try {
|
|
26
32
|
const data = JSON.parse(fs.readFileSync('$STATE_FILE', 'utf8'));
|
|
27
|
-
|
|
33
|
+
// These are the keys src/npm-package/lib/sprint-init.js writes.
|
|
34
|
+
const requiredKeys = ['id', 'task_description', 'phase', 'status'];
|
|
28
35
|
const missing = requiredKeys.filter(k => !(k in data));
|
|
29
36
|
if (missing.length > 0) {
|
|
30
37
|
console.error('ERROR: Missing required keys in sprint-state.json:', missing.join(', '));
|
|
31
38
|
process.exit(1);
|
|
32
39
|
}
|
|
40
|
+
// phase_history is an array by construction; a non-array here means the file
|
|
41
|
+
// was hand-edited or written by a different tool, which is worth failing on.
|
|
42
|
+
if (!Array.isArray(data.phase_history)) {
|
|
43
|
+
console.error('ERROR: phase_history must be an array in sprint-state.json');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
33
46
|
console.log('OK: sprint-state.json is valid with all required keys');
|
|
34
47
|
} catch (e) {
|
|
35
48
|
console.error('ERROR: sprint-state.json is not valid JSON:', e.message);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-11-01
|
|
4
|
+
**Commit:** effea31d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.19.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
|
-
**Generated:** 2026-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-11-01
|
|
4
|
+
**Commit:** effea31d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.19.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.
|
|
@@ -9,6 +9,16 @@ const fs = require('fs');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const os = require('os');
|
|
11
11
|
|
|
12
|
+
// Budget for every subprocess this file spawns, and for the vitest test that
|
|
13
|
+
// wraps it. It is deliberately far above the measured cost (the sync takes
|
|
14
|
+
// ~1.4 s here) because Stryker's dry run starts four vitest workers at once
|
|
15
|
+
// with perTest coverage: under that contention a 15 s budget made these tests
|
|
16
|
+
// fail intermittently on CI while passing locally, and the failure surfaced as
|
|
17
|
+
// "sync script must exit 0" -- which reads like a sync defect rather than a
|
|
18
|
+
// starved machine. A sync that genuinely fails still fails fast; only a starved
|
|
19
|
+
// one waits.
|
|
20
|
+
const SYNC_BUDGET_MS = 120000;
|
|
21
|
+
|
|
12
22
|
const { checkDocsDrift, checkAdapterDrift } = require('../sync-package-content');
|
|
13
23
|
|
|
14
24
|
function createTempDir() {
|
|
@@ -19,12 +29,24 @@ function cleanupTempDir(dir) {
|
|
|
19
29
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
20
30
|
}
|
|
21
31
|
|
|
32
|
+
// Fixture writers. These run in `beforeAll`; an uncaught write failure there
|
|
33
|
+
// surfaces as "sync script must exit 0" or as a missing-file assertion, neither
|
|
34
|
+
// of which names the file that could not be written. Wrapping the I/O once keeps
|
|
35
|
+
// every fixture helper reporting what actually failed.
|
|
36
|
+
function writeFixture(filePath, content) {
|
|
37
|
+
try {
|
|
38
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
39
|
+
} catch (err) {
|
|
40
|
+
throw new Error(`Cannot write test fixture ${filePath}: ${err.message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
22
44
|
function writeScript(filePath, content) {
|
|
23
|
-
|
|
45
|
+
writeFixture(filePath, content);
|
|
24
46
|
}
|
|
25
47
|
|
|
26
48
|
function writeDoc(filePath, content) {
|
|
27
|
-
|
|
49
|
+
writeFixture(filePath, content);
|
|
28
50
|
}
|
|
29
51
|
|
|
30
52
|
/**
|
|
@@ -35,26 +57,58 @@ function writeDoc(filePath, content) {
|
|
|
35
57
|
* list from rotting: syncModules() gains a module, this helper needs no edit.
|
|
36
58
|
* Only reads from the real checkout -- nothing is ever written back to it.
|
|
37
59
|
*/
|
|
60
|
+
/**
|
|
61
|
+
* Copy every `src/` module except the package itself.
|
|
62
|
+
*
|
|
63
|
+
* Does `name` under `srcRoot` need copying into the package's source seed?
|
|
64
|
+
*
|
|
65
|
+
* The package itself is the DESTINATION of the sync, not a canonical source;
|
|
66
|
+
* copying it would let the test compare a mirror against a stale mirror. Only
|
|
67
|
+
* directories are copied -- a stray file at that level is not a module.
|
|
68
|
+
*
|
|
69
|
+
* Answers false rather than throwing on an unreadable entry: an entry that cannot
|
|
70
|
+
* be stat'd is not a directory this helper can copy, and `seedSrcModules` reports
|
|
71
|
+
* the resulting gap against the path it was seeding. Swallowing this into a throw
|
|
72
|
+
* here would leave the caller unable to name which tree was incomplete.
|
|
73
|
+
*/
|
|
74
|
+
function isSeedableModule(srcRoot, name) {
|
|
75
|
+
if (name === 'npm-package') return false;
|
|
76
|
+
try {
|
|
77
|
+
return fs.statSync(path.join(srcRoot, name)).isDirectory();
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function seedSrcModules(srcRoot, dest) {
|
|
84
|
+
try {
|
|
85
|
+
for (const name of fs.readdirSync(srcRoot)) {
|
|
86
|
+
if (!isSeedableModule(srcRoot, name)) continue;
|
|
87
|
+
fs.cpSync(path.join(srcRoot, name), path.join(dest, 'src', name), { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
throw new Error(`Cannot copy src modules from ${srcRoot} into ${dest}: ${err.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
38
94
|
function seedCanonicalSources(realRoot, destRoot) {
|
|
39
95
|
const real = realRoot || path.resolve(__dirname, '..', '..', '..', '..');
|
|
40
96
|
const dest = destRoot;
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const srcRoot = path.join(real, 'src');
|
|
46
|
-
for (const name of fs.readdirSync(srcRoot)) {
|
|
47
|
-
// The package itself is the DESTINATION of the sync, not a canonical source;
|
|
48
|
-
// copying it would let the test compare a mirror against a stale mirror.
|
|
49
|
-
if (name === 'npm-package') continue;
|
|
50
|
-
const from = path.join(srcRoot, name);
|
|
51
|
-
if (fs.statSync(from).isDirectory()) {
|
|
52
|
-
fs.cpSync(from, path.join(dest, 'src', name), { recursive: true });
|
|
97
|
+
try {
|
|
98
|
+
for (const tree of ['githooks', 'skills', 'plugins']) {
|
|
99
|
+
const from = path.join(real, tree);
|
|
100
|
+
if (fs.existsSync(from)) fs.cpSync(from, path.join(dest, tree), { recursive: true });
|
|
53
101
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
102
|
+
const srcRoot = path.join(real, 'src');
|
|
103
|
+
seedSrcModules(srcRoot, dest);
|
|
104
|
+
fs.mkdirSync(path.join(dest, 'src', 'npm-package', 'scripts'), { recursive: true });
|
|
105
|
+
for (const rel of ['scripts/sync-package-content.js', 'package.json']) {
|
|
106
|
+
fs.copyFileSync(path.join(srcRoot, 'npm-package', rel), path.join(dest, 'src', 'npm-package', rel));
|
|
107
|
+
}
|
|
108
|
+
} catch (err) {
|
|
109
|
+
// A half-seeded worktree makes the sync mirror a partial tree, which then
|
|
110
|
+
// reads as a sync defect. Name the seeding direction instead.
|
|
111
|
+
throw new Error(`Cannot seed canonical sources from ${real} into ${dest}: ${err.message}`);
|
|
58
112
|
}
|
|
59
113
|
}
|
|
60
114
|
|
|
@@ -329,17 +383,21 @@ describe('syncHooks', () => {
|
|
|
329
383
|
*/
|
|
330
384
|
function seedTempWorktree() {
|
|
331
385
|
seedCanonicalSources(REAL_REPO_ROOT, WORKTREE_PATH);
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
386
|
+
try {
|
|
387
|
+
fs.mkdirSync(path.join(PKG_ROOT, 'scripts'), { recursive: true });
|
|
388
|
+
fs.copyFileSync(
|
|
389
|
+
path.join(REAL_REPO_ROOT, 'src', 'npm-package', 'scripts', 'sync-package-content.js'),
|
|
390
|
+
syncScript,
|
|
391
|
+
);
|
|
392
|
+
// The script resolves its package root relative to its own location, so it
|
|
393
|
+
// must sit at <temp>/src/npm-package/scripts/ exactly as in the real tree.
|
|
394
|
+
fs.copyFileSync(
|
|
395
|
+
path.join(REAL_REPO_ROOT, 'src', 'npm-package', 'package.json'),
|
|
396
|
+
path.join(PKG_ROOT, 'package.json'),
|
|
397
|
+
);
|
|
398
|
+
} catch (err) {
|
|
399
|
+
throw new Error(`Cannot stage the sync script into ${PKG_ROOT}: ${err.message}`);
|
|
400
|
+
}
|
|
343
401
|
}
|
|
344
402
|
|
|
345
403
|
beforeAll(() => {
|
|
@@ -356,14 +414,20 @@ describe('syncHooks', () => {
|
|
|
356
414
|
const result = spawnSync(nodeExe, [syncScript], {
|
|
357
415
|
cwd: WORKTREE_PATH,
|
|
358
416
|
stdio: 'pipe',
|
|
359
|
-
timeout:
|
|
417
|
+
timeout: SYNC_BUDGET_MS,
|
|
360
418
|
});
|
|
361
419
|
syncOk = result.status === 0;
|
|
362
420
|
if (!syncOk) {
|
|
421
|
+
// Distinguish "ran and failed" from "never got to run": the latter is an
|
|
422
|
+
// infrastructure problem and reporting it as a plain non-zero exit hides
|
|
423
|
+
// that, exactly as it did while this was misdiagnosed as a sync defect.
|
|
424
|
+
if (result.signal) console.error('sync killed by signal:', result.signal);
|
|
425
|
+
if (result.error) console.error('sync spawn error:', result.error.message);
|
|
426
|
+
console.error('sync status:', result.status);
|
|
363
427
|
console.error('sync stderr:', result.stderr?.toString() || '(empty)');
|
|
364
428
|
console.error('sync stdout:', result.stdout?.toString() || '(empty)');
|
|
365
429
|
}
|
|
366
|
-
},
|
|
430
|
+
}, SYNC_BUDGET_MS);
|
|
367
431
|
|
|
368
432
|
it('syncHooks copies pre-commit to hooks dir', () => {
|
|
369
433
|
expect(syncOk, 'sync script must exit 0').toBe(true);
|
|
@@ -421,7 +485,7 @@ describe('syncHooks', () => {
|
|
|
421
485
|
const result = spawnSync(nodeExe, [syncScript], {
|
|
422
486
|
cwd: WORKTREE_PATH,
|
|
423
487
|
stdio: 'pipe',
|
|
424
|
-
timeout:
|
|
488
|
+
timeout: SYNC_BUDGET_MS,
|
|
425
489
|
});
|
|
426
490
|
|
|
427
491
|
expect(result.status, 'sync script must exit 0').toBe(0);
|
|
@@ -439,7 +503,7 @@ describe('syncHooks', () => {
|
|
|
439
503
|
const result = spawnSync(nodeExe, [syncScript], {
|
|
440
504
|
cwd: WORKTREE_PATH,
|
|
441
505
|
stdio: 'pipe',
|
|
442
|
-
timeout:
|
|
506
|
+
timeout: SYNC_BUDGET_MS,
|
|
443
507
|
});
|
|
444
508
|
|
|
445
509
|
expect(result.status).toBe(0, 'sync script must exit 0');
|
|
@@ -565,15 +629,19 @@ describe('sync-package-content subprocess invocation', () => {
|
|
|
565
629
|
|
|
566
630
|
function seedTempWorktree() {
|
|
567
631
|
seedCanonicalSources(REAL_REPO_ROOT, WORKTREE_PATH);
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
632
|
+
try {
|
|
633
|
+
fs.mkdirSync(path.join(PKG_ROOT, 'scripts'), { recursive: true });
|
|
634
|
+
fs.copyFileSync(
|
|
635
|
+
path.join(REAL_REPO_ROOT, 'src', 'npm-package', 'scripts', 'sync-package-content.js'),
|
|
636
|
+
path.join(PKG_ROOT, 'scripts', 'sync-package-content.js'),
|
|
637
|
+
);
|
|
638
|
+
fs.copyFileSync(
|
|
639
|
+
path.join(REAL_REPO_ROOT, 'src', 'npm-package', 'package.json'),
|
|
640
|
+
path.join(PKG_ROOT, 'package.json'),
|
|
641
|
+
);
|
|
642
|
+
} catch (err) {
|
|
643
|
+
throw new Error(`Cannot stage the sync script into ${PKG_ROOT}: ${err.message}`);
|
|
644
|
+
}
|
|
577
645
|
}
|
|
578
646
|
|
|
579
647
|
beforeAll(() => {
|
|
@@ -615,7 +683,7 @@ describe('sync-package-content subprocess invocation', () => {
|
|
|
615
683
|
const result = spawnSync(nodeExe, [syncScript], {
|
|
616
684
|
cwd: WORKTREE_PATH,
|
|
617
685
|
stdio: 'pipe',
|
|
618
|
-
timeout:
|
|
686
|
+
timeout: SYNC_BUDGET_MS,
|
|
619
687
|
});
|
|
620
688
|
|
|
621
689
|
if (result.status !== 0) {
|
|
@@ -650,7 +718,7 @@ describe('sync-package-content subprocess invocation', () => {
|
|
|
650
718
|
const result = spawnSync(nodeExe, [syncScript], {
|
|
651
719
|
cwd: WORKTREE_PATH,
|
|
652
720
|
stdio: 'pipe',
|
|
653
|
-
timeout:
|
|
721
|
+
timeout: SYNC_BUDGET_MS,
|
|
654
722
|
});
|
|
655
723
|
|
|
656
724
|
if (result.status !== 0) {
|
|
@@ -678,7 +746,7 @@ describe('sync-package-content subprocess invocation', () => {
|
|
|
678
746
|
const result = spawnSync(nodeExe, [syncScript], {
|
|
679
747
|
cwd: WORKTREE_PATH,
|
|
680
748
|
stdio: 'pipe',
|
|
681
|
-
timeout:
|
|
749
|
+
timeout: SYNC_BUDGET_MS,
|
|
682
750
|
});
|
|
683
751
|
|
|
684
752
|
if (result.status !== 0) {
|
|
@@ -710,7 +778,7 @@ describe('sync-package-content subprocess invocation', () => {
|
|
|
710
778
|
const result = spawnSync(nodeExe, [syncScript], {
|
|
711
779
|
cwd: WORKTREE_PATH,
|
|
712
780
|
stdio: 'pipe',
|
|
713
|
-
timeout:
|
|
781
|
+
timeout: SYNC_BUDGET_MS,
|
|
714
782
|
});
|
|
715
783
|
|
|
716
784
|
if (result.status !== 0) {
|