@boyingliu01/xp-gate 0.9.3 → 0.9.5

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 (38) hide show
  1. package/lib/__tests__/baseline.test.js +89 -0
  2. package/lib/__tests__/doctor-helpers.test.js +139 -0
  3. package/lib/__tests__/upgrade-exec.test.js +42 -33
  4. package/lib/baseline.js +75 -48
  5. package/lib/doctor.js +199 -96
  6. package/lib/install-skill.js +81 -62
  7. package/lib/shared-phase-constants.d.ts +17 -0
  8. package/lib/shared-phase-constants.js +77 -0
  9. package/lib/shared-utils.js +14 -3
  10. package/lib/sprint-status.js +75 -83
  11. package/lib/upgrade.js +128 -85
  12. package/mock-policy/AGENTS.md +3 -3
  13. package/mutation/AGENTS.md +3 -3
  14. package/package.json +1 -1
  15. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  16. package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
  17. package/plugins/claude-code/skills/delphi-review/SKILL.md +34 -0
  18. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
  19. package/plugins/claude-code/skills/sprint-flow/SKILL.md +53 -1
  20. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  21. package/plugins/opencode/__tests__/xp-gate-update.test.ts +123 -4
  22. package/plugins/opencode/index.ts +27 -3
  23. package/plugins/opencode/package.json +1 -1
  24. package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
  25. package/plugins/opencode/skills/delphi-review/SKILL.md +34 -0
  26. package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
  27. package/plugins/opencode/skills/sprint-flow/SKILL.md +53 -1
  28. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  29. package/plugins/opencode/tui-plugin.ts +49 -76
  30. package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
  31. package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
  32. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  33. package/principles/AGENTS.md +3 -3
  34. package/skills/delphi-review/AGENTS.md +3 -3
  35. package/skills/delphi-review/SKILL.md +34 -0
  36. package/skills/sprint-flow/AGENTS.md +3 -3
  37. package/skills/sprint-flow/SKILL.md +53 -1
  38. package/skills/test-specification-alignment/AGENTS.md +3 -3
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-06-18
4
- **Commit:** b67fc85
3
+ **Generated:** 2026-06-21
4
+ **Commit:** 1050a30
5
5
  **Branch:** main
6
- **Version:** 0.9.3.0
6
+ **Version:** 0.9.5.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -14,7 +14,7 @@ import { randomUUID } from "node:crypto"
14
14
  import { mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from "node:fs"
15
15
  import { join } from "node:path"
16
16
  import { tmpdir, homedir } from "node:os"
17
- import { execSync } from "node:child_process"
17
+ import { execSync, spawn } from "node:child_process"
18
18
 
19
19
  // ── Pure function: semverLt ──
20
20
 
@@ -153,14 +153,19 @@ async function checkXpGateUpdate(): Promise<UpgradeResult> {
153
153
  return { action: "noop", localVersion, remoteVersion }
154
154
  }
155
155
 
156
- // 5. Outdated — auto upgrade
156
+ // 5. Outdated — auto upgrade (non-blocking spawn)
157
157
  writeXpGateCache({ ts: Date.now(), localVersion, remoteVersion })
158
158
  try {
159
- execSync(`npm install -g ${XP_GATE_NPM_PKG}@${remoteVersion}`, {
159
+ const child = spawn("npm", ["install", "-g", `${XP_GATE_NPM_PKG}@${remoteVersion}`], {
160
160
  stdio: "pipe",
161
161
  timeout: 120_000,
162
162
  })
163
- writeXpGateCache({ ts: Date.now(), localVersion: remoteVersion, remoteVersion, status: "current" })
163
+ child.on("close", (code) => {
164
+ if (code === 0) {
165
+ writeXpGateCache({ ts: Date.now(), localVersion: remoteVersion, remoteVersion, status: "current" })
166
+ }
167
+ })
168
+ child.on("error", () => { /* empty — cache won't get status:current, so next check retries */ })
164
169
  return { action: "upgraded", localVersion, remoteVersion }
165
170
  } catch (err) {
166
171
  const msg = err instanceof Error ? err.message : String(err)
@@ -168,6 +173,21 @@ async function checkXpGateUpdate(): Promise<UpgradeResult> {
168
173
  }
169
174
  }
170
175
 
176
+ /**
177
+ * Read xp-gate config.json for opt-out settings.
178
+ * Returns null if config doesn't exist or is malformed.
179
+ */
180
+ function readXpGateConfig(): { autoUpgrade?: boolean } | null {
181
+ const cfgPath = join(homedir(), ".xp-gate", "config.json")
182
+ try {
183
+ if (!existsSync(cfgPath)) return null
184
+ const raw = readFileSync(cfgPath, "utf8")
185
+ return JSON.parse(raw)
186
+ } catch {
187
+ return null
188
+ }
189
+ }
190
+
171
191
  // ── Tests ──
172
192
 
173
193
  void describe("semverLt", () => {
@@ -253,3 +273,102 @@ void describe("checkXpGateUpdate — cache & upgrade", () => {
253
273
  assert.equal(result.action, "noop")
254
274
  })
255
275
  })
276
+
277
+
278
+ // ── UPG-002: spawn-based upgrade tests ──
279
+
280
+ void describe("checkXpGateUpdate — spawn (UPG-002)", () => {
281
+ const fakeHome = join(tmpdir(), "xp-gate-upd-spawn-" + randomUUID())
282
+ const origHome = process.env.HOME
283
+
284
+ before(() => {
285
+ process.env.HOME = fakeHome
286
+ mkdirSync(join(fakeHome, ".xp-gate"), { recursive: true })
287
+ })
288
+
289
+ after(() => {
290
+ process.env.HOME = origHome
291
+ rmSync(fakeHome, { recursive: true, force: true })
292
+ })
293
+
294
+ void it("returns upgraded with spawn-based npm install", async () => {
295
+ const cachePath = join(fakeHome, ".xp-gate", "xp-gate-version-check.json")
296
+ writeFileSync(cachePath, JSON.stringify({
297
+ ts: Date.now() - 86_400_000 - 3600_000, // stale
298
+ localVersion: "0.9.1",
299
+ remoteVersion: "0.9.2",
300
+ }))
301
+
302
+ const result = await checkXpGateUpdate()
303
+ assert.ok(["noop", "upgraded", "error"].includes(result.action))
304
+ })
305
+ })
306
+
307
+ // ── UPG-003: readXpGateConfig isolated tests ──
308
+
309
+ void describe("readXpGateConfig", () => {
310
+ const fakeHome = join(tmpdir(), "xp-gate-upd-cfg-" + randomUUID())
311
+ const origHome = process.env.HOME
312
+
313
+ before(() => {
314
+ process.env.HOME = fakeHome
315
+ mkdirSync(join(fakeHome, ".xp-gate"), { recursive: true })
316
+ })
317
+
318
+ after(() => {
319
+ process.env.HOME = origHome
320
+ rmSync(fakeHome, { recursive: true, force: true })
321
+ })
322
+
323
+ void it("returns null when no config.json exists", () => {
324
+ assert.equal(readXpGateConfig(), null)
325
+ })
326
+
327
+ void it("returns parsed config when config.json exists with autoUpgrade false", () => {
328
+ const cfgPath = join(fakeHome, ".xp-gate", "config.json")
329
+ writeFileSync(cfgPath, JSON.stringify({ autoUpgrade: false }))
330
+ const cfg = readXpGateConfig()
331
+ assert.notEqual(cfg, null)
332
+ assert.equal(cfg!.autoUpgrade, false)
333
+ })
334
+
335
+ void it("returns parsed config when config.json exists with autoUpgrade true", () => {
336
+ const cfgPath = join(fakeHome, ".xp-gate", "config.json")
337
+ writeFileSync(cfgPath, JSON.stringify({ autoUpgrade: true }))
338
+ const cfg = readXpGateConfig()
339
+ assert.notEqual(cfg, null)
340
+ assert.equal(cfg!.autoUpgrade, true)
341
+ })
342
+
343
+ void it("returns null when config.json is malformed", () => {
344
+ const cfgPath = join(fakeHome, ".xp-gate", "config.json")
345
+ writeFileSync(cfgPath, "not-json")
346
+ assert.equal(readXpGateConfig(), null)
347
+ })
348
+ })
349
+
350
+ // ── UPG-003: opt-out config integration tests ──
351
+
352
+ void describe("checkXpGateUpdate — opt-out config integration (UPG-003)", () => {
353
+ const fakeHome = join(tmpdir(), "xp-gate-upd-cfg-int-" + randomUUID())
354
+ const origHome = process.env.HOME
355
+
356
+ before(() => {
357
+ process.env.HOME = fakeHome
358
+ mkdirSync(join(fakeHome, ".xp-gate"), { recursive: true })
359
+ })
360
+
361
+ after(() => {
362
+ process.env.HOME = origHome
363
+ rmSync(fakeHome, { recursive: true, force: true })
364
+ })
365
+
366
+ void it("handles config.json with autoUpgrade false (no crash, graceful noop)", async () => {
367
+ const cfgPath = join(fakeHome, ".xp-gate", "config.json")
368
+ writeFileSync(cfgPath, JSON.stringify({ autoUpgrade: false }))
369
+
370
+ const result = await checkXpGateUpdate()
371
+ // Should not crash — returns noop because local install not found in fake home
372
+ assert.ok(["noop", "upgraded", "error"].includes(result.action))
373
+ })
374
+ })
@@ -1,6 +1,6 @@
1
1
  import { tool } from "@opencode-ai/plugin"
2
2
  import { z } from "zod"
3
- import { exec, execSync } from "node:child_process"
3
+ import { exec, execSync, spawn } from "node:child_process"
4
4
  import { promisify } from "node:util"
5
5
  import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "node:fs"
6
6
  import { join } from "node:path"
@@ -115,10 +115,25 @@ async function checkXpGateUpdate(): Promise<UpgradeResult> {
115
115
  return { action: "noop", localVersion, remoteVersion }
116
116
  }
117
117
 
118
+ // Check opt-out config
119
+ const config = readXpGateConfig()
120
+ if (config?.autoUpgrade === false) {
121
+ writeCache(XP_GATE_CACHE_FILE, { ts: Date.now(), localVersion, remoteVersion, status: "current" })
122
+ return { action: "noop", localVersion, remoteVersion }
123
+ }
124
+
118
125
  writeCache(XP_GATE_CACHE_FILE, { ts: Date.now(), localVersion, remoteVersion })
119
126
  try {
120
- execSync(`npm install -g ${XP_GATE_NPM_PKG}@${remoteVersion}`, { stdio: "pipe", timeout: 120_000 })
121
- writeCache(XP_GATE_CACHE_FILE, { ts: Date.now(), localVersion: remoteVersion, remoteVersion, status: "current" })
127
+ const child = spawn("npm", ["install", "-g", `${XP_GATE_NPM_PKG}@${remoteVersion}`], {
128
+ stdio: "pipe",
129
+ timeout: 120_000,
130
+ })
131
+ child.on("close", (code) => {
132
+ if (code === 0) {
133
+ writeCache(XP_GATE_CACHE_FILE, { ts: Date.now(), localVersion: remoteVersion, remoteVersion, status: "current" })
134
+ }
135
+ })
136
+ child.on("error", () => { /* empty — cache won't get status:current, so next check retries */ })
122
137
  return { action: "upgraded", localVersion, remoteVersion }
123
138
  } catch (err) {
124
139
  const msg = err instanceof Error ? err.message : String(err)
@@ -126,6 +141,15 @@ async function checkXpGateUpdate(): Promise<UpgradeResult> {
126
141
  }
127
142
  }
128
143
 
144
+ function readXpGateConfig(): { autoUpgrade?: boolean } | null {
145
+ const cfgPath = join(homedir(), ".xp-gate", "config.json")
146
+ try {
147
+ if (!existsSync(cfgPath)) return null
148
+ return JSON.parse(readFileSync(cfgPath, "utf8"))
149
+ } catch {
150
+ return null
151
+ }
152
+ }
129
153
  // ── OpenCode plugin version check (notification only) ──
130
154
 
131
155
  async function checkPluginUpdate(pluginDir: string): Promise<void> {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/opencode-plugin",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
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
- **Generated:** 2026-06-18
4
- **Commit:** b67fc85
3
+ **Generated:** 2026-06-21
4
+ **Commit:** 1050a30
5
5
  **Branch:** main
6
- **Version:** 0.9.3.0
6
+ **Version:** 0.9.5.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: delphi-review
3
3
  description: "Use when asked to review a design, plan, or architecture; before implementation starts; or when multi-expert consensus is needed. Triggers: 'review this design', '评审这个需求', 'design review', '多专家评审', 'consensus review', 'code walkthrough', 'push review', 'architecture review', 'PR review', or any request for multi-expert evaluation of requirements, design docs, or PRs."
4
+ auto_continue: true
4
5
  ---
5
6
 
6
7
  # Delphi Consensus Review
@@ -109,6 +110,39 @@ Phase 0: 准备 → Round 1: 匿名独立评审 → 共识检查
109
110
 
110
111
  **Automatic re-review**: 对于常见可控问题(措辞模糊、AC 缺失、格式问题),subagent 应自行修复后自动重评审,无需等待用户。
111
112
 
113
+ ### ⭐ 自动延续规则(MANDATORY — 防止流程卡住)
114
+
115
+ 当 Delphi Review 启动多轮评审(Round 2+)时,**orchestrator 必须自动延续流程,不得等待用户输入**。
116
+
117
+ **触发条件**:
118
+ - Round N 完成,但未达到终止条件(100% approved + ≥90% consensus + 所有 Critical/Major 已处理)
119
+ - 存在待处理的背景任务(subagent dispatched tasks)
120
+
121
+ **自动延续动作**:
122
+ 1. **收集背景任务结果**:等待 `<system-reminder>` 通知后,立即调用 `background_output(task_id="bg_...")` 获取所有 subagent 输出
123
+ 2. **合成 Round N 总结**:汇总专家意见、共识度、待处理问题
124
+ 3. **自动启动 Round N+1**:立即 dispatch 新一轮 subagent 任务,携带上一轮总结作为上下文
125
+ 4. **循环直至终止**:重复步骤 1-3,直到达到终止条件
126
+
127
+ **终止条件**(满足全部):
128
+ - ✅ 所有专家状态 = APPROVED
129
+ - ✅ 共识度 ≥ 90%
130
+ - ✅ 所有 Critical 级别问题已解决
131
+ - ✅ 所有 Major 级别问题已处理(或已记录为已知问题)
132
+
133
+ **例外情况**(直接输出,不进入下一轮):
134
+ - Round 1 即达到 100% approved + 100% consensus → 直接输出最终报告
135
+ - 已达最大轮数(5 轮)→ 输出"未达成共识"报告,标记为 PROCESS_BLOCK
136
+
137
+ **禁止行为**:
138
+ - ❌ 询问用户"要继续吗?"
139
+ - ❌ 等待用户手动触发 Round N+1
140
+ - ❌ 在未达到终止条件时停止流程
141
+
142
+ **错误处理**:
143
+ - 背景任务超时(>10min)→ 标记为 TIMEOUT,输出部分结果并终止
144
+ - 背景任务失败(subagent 错误)→ 重试 1 次,仍失败则输出错误报告并终止
145
+
112
146
  ---
113
147
 
114
148
  ## 修复与重新评审
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-06-18
4
- **Commit:** b67fc85
3
+ **Generated:** 2026-06-21
4
+ **Commit:** 1050a30
5
5
  **Branch:** main
6
- **Version:** 0.9.3.0
6
+ **Version:** 0.9.5.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **11-phase** development pipeline: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → USER ACCEPTANCE → FEEDBACK → SHIP → LAND → CLEANUP. Phase 2 default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE in Phase 1: design must pass Delphi review (≥90% consensus) before any coding.
@@ -408,6 +408,58 @@ Sprint Flow: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW
408
408
 
409
409
  ---
410
410
 
411
+ ### ⭐ Phase State Persistence(阶段状态持久化 — MANDATORY)
412
+
413
+ **编排器必须在每个 Phase 完成后更新 `.sprint-state/sprint-state.json`**:
414
+
415
+ 1. **Phase 完成后立即更新**(每个 Phase 结束前):
416
+ - `phase`: 更新为当前 Phase 编号(如 `0`, `1`, `2`...)
417
+ - `status`: 更新为 `"completed"`(已完成 Phase)
418
+ - `phase_history`: 追加新条目
419
+
420
+ 2. **`phase_history` 数组条目 schema**:
421
+ ```json
422
+ {
423
+ "phase": 0,
424
+ "phase_name": "THINK",
425
+ "status": "completed",
426
+ "timestamp": "2026-06-20T10:30:00Z"
427
+ }
428
+ ```
429
+
430
+ 3. **检查点**:
431
+ - `--status` 参数读取 `sprint-state.json` 并渲染进度看板
432
+ - TUI panel 显示当前 Phase 和历史
433
+ - `--resume-from` 校验 `phase_history` 中的最后完成 Phase
434
+
435
+ 4. **完整 sprint-state.json 示例**:
436
+ ```json
437
+ {
438
+ "id": "sprint-2026-06-20-01",
439
+ "phase": 2,
440
+ "status": "in_progress",
441
+ "phase_history": [
442
+ {"phase": -1, "phase_name": "ISOLATE", "status": "completed", "timestamp": "2026-06-20T10:00:00Z"},
443
+ {"phase": -0.5, "phase_name": "AUTO-ESTIMATE", "status": "completed", "timestamp": "2026-06-20T10:05:00Z"},
444
+ {"phase": 0, "phase_name": "THINK", "status": "completed", "timestamp": "2026-06-20T10:15:00Z"},
445
+ {"phase": 1, "phase_name": "PLAN", "status": "completed", "timestamp": "2026-06-20T10:30:00Z"}
446
+ ],
447
+ "isolation": {
448
+ "worktree_path": "/home/boyingliu01/projects/xp-gate/.worktrees/sprint/sprint-2026-06-20-01",
449
+ "branch": "sprint/2026-06-20-01"
450
+ },
451
+ "outputs": {
452
+ "pain_document": "phase-outputs/phase-0-summary.md",
453
+ "specification": "phase-outputs/specification.yaml"
454
+ },
455
+ "metrics": {
456
+ "coverage_pct": 85.5
457
+ }
458
+ }
459
+ ```
460
+
461
+ ---
462
+
411
463
  ## 使用示例
412
464
 
413
465
  | 场景 | 命令 | 说明 |
@@ -447,7 +499,7 @@ See [Output Contract](#output-contract) below for the canonical machine-readable
447
499
 
448
500
  **Phase Summary** (每个 Phase 必须输出 YAML frontmatter): `phase/N`, `phase_name`, `status`, `outputs[]`, `decisions[]`, `next_phase_context` + markdown body (≤50 lines)
449
501
 
450
- **Sprint State JSON**: `{id, phase, status, isolation {worktree_path, branch}, outputs, metrics}` — 存储于 `.sprint-state/sprint-state.json`
502
+ **Sprint State JSON**: `{id, phase, status, phase_history[], isolation {worktree_path, branch}, outputs, metrics}` — 存储于 `.sprint-state/sprint-state.json`
451
503
 
452
504
  **Final User-Facing Output**: Phase/status, file paths, validation results, next user decision, PR URL or cleanup report
453
505
 
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-06-18
4
- **Commit:** b67fc85
3
+ **Generated:** 2026-06-21
4
+ **Commit:** 1050a30
5
5
  **Branch:** main
6
- **Version:** 0.9.3.0
6
+ **Version:** 0.9.5.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -15,6 +15,7 @@
15
15
  import { existsSync, readFileSync } from "node:fs"
16
16
  import { join } from "node:path"
17
17
  import type { TuiPlugin, TuiSlotPlugin, TuiSlotProps } from "@opencode-ai/plugin/tui"
18
+ import { PHASE_NAMES, PHASE_ORDER, getLatestTimestamp, isStale } from "../../src/npm-package/lib/shared-phase-constants.js"
18
19
 
19
20
  // ── Sprint state schema ──
20
21
 
@@ -44,24 +45,6 @@ interface SprintState {
44
45
  phase_history?: SprintPhaseHistory[]
45
46
  }
46
47
 
47
- // ── Constants ──
48
-
49
- const PHASE_NAMES: Record<string, string> = {
50
- "-1": "ISOLATE",
51
- "-0.5": "AUTO-ESTIMATE",
52
- "0": "THINK",
53
- "1": "PLAN",
54
- "2": "BUILD",
55
- "3": "REVIEW",
56
- "4": "USER ACCEPT",
57
- "5": "FEEDBACK",
58
- "6": "SHIP",
59
- "7": "LAND",
60
- "8": "CLEANUP",
61
- }
62
-
63
- const PHASE_ORDER = ["-1", "-0.5", "0", "1", "2", "3", "4", "5", "6", "7", "8"]
64
-
65
48
  // ── Helpers ──
66
49
 
67
50
  function readSprintState(dir: string): SprintState | null {
@@ -74,26 +57,6 @@ function readSprintState(dir: string): SprintState | null {
74
57
  }
75
58
  }
76
59
 
77
- function isStale(state: SprintState): boolean {
78
- if (!state || !state.started_at) return false
79
- const started = new Date(state.started_at).getTime()
80
- if (isNaN(started)) return false
81
- let latest = started
82
- if (Array.isArray(state.phase_history)) {
83
- for (const ph of state.phase_history) {
84
- if (ph.completed_at) {
85
- const t = new Date(ph.completed_at).getTime()
86
- if (!isNaN(t) && t > latest) latest = t
87
- }
88
- if (ph.started_at) {
89
- const t = new Date(ph.started_at).getTime()
90
- if (!isNaN(t) && t > latest) latest = t
91
- }
92
- }
93
- }
94
- return Date.now() - latest > 3_600_000
95
- }
96
-
97
60
  function statusSymbol(status: string | undefined, key: string, currentPhase: string | number | undefined): string {
98
61
  if (status === "completed") return "✓"
99
62
  if (status === "in_progress") return "→"
@@ -109,57 +72,67 @@ function renderPhaseLine(key: string, history: SprintPhaseHistory | undefined, c
109
72
  .replace(/\s+$/, "")
110
73
  }
111
74
 
112
- function renderSprintSidebar(state: SprintState): string {
113
- if (!state || !state.task_description) return ""
114
-
115
- const lines: string[] = []
116
- const metrics = state.metrics || {}
117
- const currentPhase = state.phase
118
-
119
- // Build lookup
120
- const historyByPhase: Record<string, SprintPhaseHistory> = {}
75
+ function buildPhaseLookup(state: SprintState): Record<string, SprintPhaseHistory> {
76
+ const lookup: Record<string, SprintPhaseHistory> = {}
121
77
  if (Array.isArray(state.phase_history)) {
122
78
  for (const ph of state.phase_history) {
123
- historyByPhase[String(ph.phase)] = ph
79
+ lookup[String(ph.phase)] = ph
124
80
  }
125
81
  }
82
+ return lookup
83
+ }
84
+
85
+ function buildMetricsLine(metrics: SprintState["metrics"]): string | null {
86
+ const parts: string[] = []
87
+ if (metrics?.tests_passed != null) parts.push(`tests:${metrics.tests_passed}`)
88
+ if (metrics?.coverage_pct != null) parts.push(`cov:${metrics.coverage_pct}%`)
89
+ return parts.length > 0 ? parts.join(" ") : null
90
+ }
126
91
 
127
- // Title
128
- lines.push(`SPRINT: ${state.task_description}`)
92
+ function buildStaleWarning(state: SprintState): string | null {
93
+ if (!state || !state.started_at) return null
94
+ return isStale(state) ? "⚠ idle >1h" : null
95
+ }
129
96
 
130
- // Metrics
131
- const metricParts: string[] = []
132
- if (metrics.tests_passed != null) {
133
- metricParts.push(`tests:${metrics.tests_passed}`)
134
- }
135
- if (metrics.coverage_pct != null) {
136
- metricParts.push(`cov:${metrics.coverage_pct}%`)
137
- }
138
- if (metricParts.length > 0) {
139
- lines.push(metricParts.join(" "))
140
- }
97
+ function renderBuildReqs(history: SprintPhaseHistory): string[] {
98
+ if (!history.reqs) return []
99
+ return Object.entries(history.reqs)
100
+ .filter(([, r]) => r.name)
101
+ .map(([id, r]) => ` ${statusSymbol(r.status, id, undefined)} ${r.name}`)
102
+ }
141
103
 
142
- // Stale warning
143
- if (isStale(state)) {
144
- lines.push("⚠ idle >1h")
104
+ function appendBuildReqs(lines: string[], key: string, history: SprintPhaseHistory | undefined): void {
105
+ if (key === "2" && history?.reqs) {
106
+ lines.push(...renderBuildReqs(history))
145
107
  }
108
+ }
146
109
 
147
- // Phase progress
110
+ function renderPhaseLines(
111
+ historyByPhase: Record<string, SprintPhaseHistory>,
112
+ currentPhase: string | number | undefined,
113
+ ): string[] {
114
+ const lines: string[] = []
148
115
  for (const key of PHASE_ORDER) {
149
116
  const history = historyByPhase[key]
150
- // Only show phases with activity or current
151
117
  if (!history && String(currentPhase) !== key) continue
152
- const line = renderPhaseLine(key, history, currentPhase)
153
- lines.push(line)
154
-
155
- // REQ-level progress for BUILD phase
156
- if (key === "2" && history?.reqs) {
157
- const reqNames = Object.entries(history.reqs)
158
- .filter(([, r]) => r.name)
159
- .map(([id, r]) => ` ${statusSymbol(r.status, id, undefined)} ${r.name}`)
160
- if (reqNames.length > 0) lines.push(...reqNames)
161
- }
118
+ lines.push(renderPhaseLine(key, history, currentPhase))
119
+ appendBuildReqs(lines, key, history)
162
120
  }
121
+ return lines
122
+ }
123
+
124
+ function renderSprintSidebar(state: SprintState): string {
125
+ if (!state || !state.task_description) return ""
126
+
127
+ const lines: string[] = [`SPRINT: ${state.task_description}`]
128
+ const metricsLine = buildMetricsLine(state.metrics)
129
+ if (metricsLine) lines.push(metricsLine)
130
+
131
+ const staleWarning = buildStaleWarning(state)
132
+ if (staleWarning) lines.push(staleWarning)
133
+
134
+ const historyByPhase = buildPhaseLookup(state)
135
+ lines.push(...renderPhaseLines(historyByPhase, state.phase))
163
136
 
164
137
  return lines.join("\n")
165
138
  }
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-06-18
4
- **Commit:** b67fc85
3
+ **Generated:** 2026-06-21
4
+ **Commit:** 1050a30
5
5
  **Branch:** main
6
- **Version:** 0.9.3.0
6
+ **Version:** 0.9.5.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-06-18
4
- **Commit:** b67fc85
3
+ **Generated:** 2026-06-21
4
+ **Commit:** 1050a30
5
5
  **Branch:** main
6
- **Version:** 0.9.3.0
6
+ **Version:** 0.9.5.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **11-phase** development pipeline: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → USER ACCEPTANCE → FEEDBACK → SHIP → LAND → CLEANUP. Phase 2 default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE in Phase 1: 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
- **Generated:** 2026-06-18
4
- **Commit:** b67fc85
3
+ **Generated:** 2026-06-21
4
+ **Commit:** 1050a30
5
5
  **Branch:** main
6
- **Version:** 0.9.3.0
6
+ **Version:** 0.9.5.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
- **Generated:** 2026-06-18
4
- **Commit:** b67fc85
3
+ **Generated:** 2026-06-21
4
+ **Commit:** 1050a30
5
5
  **Branch:** main
6
- **Version:** 0.9.3.0
6
+ **Version:** 0.9.5.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
- **Generated:** 2026-06-18
4
- **Commit:** b67fc85
3
+ **Generated:** 2026-06-21
4
+ **Commit:** 1050a30
5
5
  **Branch:** main
6
- **Version:** 0.9.3.0
6
+ **Version:** 0.9.5.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: delphi-review
3
3
  description: "Use when asked to review a design, plan, or architecture; before implementation starts; or when multi-expert consensus is needed. Triggers: 'review this design', '评审这个需求', 'design review', '多专家评审', 'consensus review', 'code walkthrough', 'push review', 'architecture review', 'PR review', or any request for multi-expert evaluation of requirements, design docs, or PRs."
4
+ auto_continue: true
4
5
  ---
5
6
 
6
7
  # Delphi Consensus Review
@@ -109,6 +110,39 @@ Phase 0: 准备 → Round 1: 匿名独立评审 → 共识检查
109
110
 
110
111
  **Automatic re-review**: 对于常见可控问题(措辞模糊、AC 缺失、格式问题),subagent 应自行修复后自动重评审,无需等待用户。
111
112
 
113
+ ### ⭐ 自动延续规则(MANDATORY — 防止流程卡住)
114
+
115
+ 当 Delphi Review 启动多轮评审(Round 2+)时,**orchestrator 必须自动延续流程,不得等待用户输入**。
116
+
117
+ **触发条件**:
118
+ - Round N 完成,但未达到终止条件(100% approved + ≥90% consensus + 所有 Critical/Major 已处理)
119
+ - 存在待处理的背景任务(subagent dispatched tasks)
120
+
121
+ **自动延续动作**:
122
+ 1. **收集背景任务结果**:等待 `<system-reminder>` 通知后,立即调用 `background_output(task_id="bg_...")` 获取所有 subagent 输出
123
+ 2. **合成 Round N 总结**:汇总专家意见、共识度、待处理问题
124
+ 3. **自动启动 Round N+1**:立即 dispatch 新一轮 subagent 任务,携带上一轮总结作为上下文
125
+ 4. **循环直至终止**:重复步骤 1-3,直到达到终止条件
126
+
127
+ **终止条件**(满足全部):
128
+ - ✅ 所有专家状态 = APPROVED
129
+ - ✅ 共识度 ≥ 90%
130
+ - ✅ 所有 Critical 级别问题已解决
131
+ - ✅ 所有 Major 级别问题已处理(或已记录为已知问题)
132
+
133
+ **例外情况**(直接输出,不进入下一轮):
134
+ - Round 1 即达到 100% approved + 100% consensus → 直接输出最终报告
135
+ - 已达最大轮数(5 轮)→ 输出"未达成共识"报告,标记为 PROCESS_BLOCK
136
+
137
+ **禁止行为**:
138
+ - ❌ 询问用户"要继续吗?"
139
+ - ❌ 等待用户手动触发 Round N+1
140
+ - ❌ 在未达到终止条件时停止流程
141
+
142
+ **错误处理**:
143
+ - 背景任务超时(>10min)→ 标记为 TIMEOUT,输出部分结果并终止
144
+ - 背景任务失败(subagent 错误)→ 重试 1 次,仍失败则输出错误报告并终止
145
+
112
146
  ---
113
147
 
114
148
  ## 修复与重新评审