@boyingliu01/xp-gate 0.10.16 → 0.11.0

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.
@@ -19,6 +19,15 @@ auto_continue: true
19
19
  - Does NOT replace testing or CI/CD verification
20
20
  - Does NOT handle deployment or release decisions
21
21
 
22
+ ## Activation
23
+ **MANDATORY**: Every delphi-review response MUST begin with `[DelphiReview]` as the first line.
24
+ This marker is required for skill-cert L1 trigger detection.
25
+
26
+ Permitted variants (all satisfy L1 trigger):
27
+ - `[DelphiReview]` — standard entry
28
+ - `[DelphiReview:BLOCKED]` — Step 0 input validation failure
29
+ - `[DelphiReview:WARNING]` — red flag detected (reserved)
30
+
22
31
  ## 核心原则
23
32
 
24
33
  **Delphi 方法只有一个目的:得到所有专家一致认可的可行方案。**
@@ -104,6 +113,46 @@ Phase 0: 准备 → Round 1: 匿名独立评审 → 共识检查
104
113
  └─ REQUEST_CHANGES → 修复方案 → 回到 Round 2 重新评审
105
114
  ```
106
115
 
116
+ ### Step 0: Input Validation (MANDATORY — 必须在任何评审前执行)
117
+
118
+ 检查用户 prompt 中是否包含可评审内容(设计文档、代码、specification.yaml、PR diff):
119
+
120
+ 1. **有完整输入** → 直接进入 Phase 0(准备阶段),开始 Round 1
121
+ 2. **有部分输入**(占位符、描述性文本)→ 按输入内容执行评审,标注 `[INPUT: PARTIAL]`,但继续执行
122
+ 3. **无输入**(仅触发词,无文档/代码)→ 输出以下阻断响应,记入步骤完成:
123
+
124
+ **Detection heuristics for Step 0**:
125
+ - **Complete**: Contains ≥1 structural element (e.g., `##`, `requirement`, `AC-`, `function`, `class`, `interface`, YAML frontmatter, code block with language tag) AND ≥50 non-whitespace characters of actual content (not placeholder brackets like `[...]`, `{...}`, `<insert here>`).
126
+ - **Partial**: Contains descriptive text referencing a design/code artifact BUT lacks substantive structure (e.g., "I need to review my login module" with no actual code/design attached), OR contains obvious placeholders like `[...]`, `(content)`, `<insert here>`, `TODO`.
127
+ - **None**: Only trigger words (`/delphi-review`, "review this") with zero additional content, OR content that is exclusively questions about the review process itself ("how does delphi work?").
128
+
129
+ **Partial input constraint**: When input is PARTIAL, cap review to 1 round with `confidence=low` annotation. Do NOT proceed with full multi-round review on insufficient input.
130
+
131
+ **File path validation**: If user provides a file path (e.g., `--spec specification.yaml`):
132
+ - Verify the file exists and is non-empty
133
+ - If path is invalid → output: `[DelphiReview:BLOCKED] File not found: [path]. Please verify the path.`
134
+ - If file is empty → output: `[DelphiReview:BLOCKED] File is empty: [path]. Please provide valid content.`
135
+
136
+ ```
137
+ [DelphiReview:BLOCKED] 需要设计文档或代码内容才能启动评审。
138
+
139
+ 请提供以下之一:
140
+ - 设计文档(design doc / specification)
141
+ - 代码变更(code diff / PR link)
142
+ - specification.yaml 文件路径
143
+ - 架构设计说明
144
+
145
+ 评审输入示例:
146
+ /delphi-review "Design Doc: [your content here]"
147
+ /delphi-review --spec specification.yaml
148
+ /delphi-review --mode code-walkthrough
149
+ REMAINING STEPS: N/A (input validation failed)
150
+ ```
151
+
152
+ 在此状态下,BLOCKED 视为步骤已完成(后续步骤标记为 N/A)。
153
+
154
+ **重要**: 内嵌在 prompt 中的文档内容(如 "Design Doc: [content]"或代码片段)应视为"有完整输入",直接进入评审。
155
+
107
156
  **Round 模板**(匿名评审/交换意见/最终立场/修复报告格式)→ 详见 `references/round-templates.md`
108
157
 
109
158
  **Orchestrator 自动调度规则**(#218 subagent 内部自动多轮循环)→ 详见 `references/orchestrator-dispatch.md`
@@ -165,6 +214,23 @@ Phase 0: 准备 → Round 1: 匿名独立评审 → 共识检查
165
214
 
166
215
  ## Output Format (MANDATORY)
167
216
 
217
+ **Single 模式简化输出**: 当 single reviewer 模式(非 Multi-Expert)时,可使用简化输出格式:
218
+ - verdict + confidence + issues_list(合并 critical/major/minor)+ summary
219
+ - 完整 JSON 格式保留用于 multi-expert 多轮评审场景
220
+
221
+ **Single 模式简化模板**:
222
+ ```
223
+ [DelphiReview] verdict=APPROVED | REQUEST_CHANGES | BLOCKED
224
+ confidence=N/10
225
+ issues=[critical: N, major: N, minor: N]
226
+ summary: [1-2 sentence verdict summary]
227
+ ```
228
+
229
+ **⚠️ Single vs Multi-Expert Output**:
230
+ - **Multi-Expert Mode (default)**: MUST use the full JSON schema below. Each expert outputs independently; the orchestrator aggregates into `consensus_report`. DO NOT use the simplified template.
231
+ - **Single Reviewer Mode** (explicitly invoked with `--single`): MAY use the simplified text template above.
232
+ - **Never mix formats**. If you are one of multiple experts in the same round, output JSON only.
233
+
168
234
  ```json
169
235
  {
170
236
  "expert_id": "A|B|C",
@@ -243,10 +309,23 @@ Phase 0: 准备 → Round 1: 匿名独立评审 → 共识检查
243
309
 
244
310
  ## Red Flags
245
311
 
312
+ ### 检测触发器(模型可执行检测)
313
+
314
+ | 用户输入模式 | 触发词 | 响应动作 |
315
+ |-------------|--------|---------|
316
+ | 要求跳过评审 | "skip review", "不用评审", "跳过评审", "直接提交", "不评审" | → 提醒: `[DelphiReview] 评审是投资而非开销。Delphi 设计要求多轮共识(>=90%),不可快速跳过。` |
317
+ | 时间压力 | "来不及", "时间紧", "emergency", "赶时间", "deadline" | → 提醒: `[DelphiReview] 时间紧迫正是需要评审的时刻。跳过评审省 30 分钟,后期修复可能花 3 天。` |
318
+ | 提前终止 | Round 1 后用户说 "可以了", "够了", "enough" | → BLOCK: `[DelphiReview:BLOCKED] 评审未达终止条件。仍需 [共识>=90% + 所有 Critical/Major 已处理]。` |
319
+ | 单专家自评 | 用户仅指定 1 个专家 或 说 "我自己看了" | → 提醒: `[DelphiReview] 至少需要 2 位不同 provider 的专家参与评审。` |
320
+ | 无文档输入 | 仅触发词,无设计/代码内容 | → 输出 `[DelphiReview:BLOCKED]` 阻断响应(见 Step 0) |
321
+
322
+ ### 原则性声明
323
+
246
324
  | 借口 | 现实 |
247
325
  |------|------|
248
326
  | "这只是小变更" | 所有变更都需要评审 |
249
327
  | "Round 1 就够了" | 不够,必须多轮直到共识 |
328
+ | "生成报告就完成了" | APPROVED 才算完成 |
250
329
  | "2/3 同意就是共识" | 还要检查问题共识比例 >=90% |
251
330
 
252
331
  ---
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-06-25
4
- **Commit:** 463f7e6
3
+ **Generated:** 2026-06-29
4
+ **Commit:** 8efd33c
5
5
  **Branch:** main
6
- **Version:** 0.10.16.0
6
+ **Version:** 0.11.0.1
7
7
 
8
8
  ## OVERVIEW
9
9
  **11-phase** development pipeline: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → SHIP → LAND → USER ACCEPTANCE → FEEDBACK → 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-25
4
- **Commit:** 463f7e6
3
+ **Generated:** 2026-06-29
4
+ **Commit:** 8efd33c
5
5
  **Branch:** main
6
- **Version:** 0.10.16.0
6
+ **Version:** 0.11.0.1
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/opencode-plugin",
3
- "version": "0.10.16",
3
+ "version": "0.11.0",
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",
@@ -19,8 +19,8 @@
19
19
  "types": "./index.ts"
20
20
  },
21
21
  "./tui": {
22
- "import": "./tui-plugin.ts",
23
- "types": "./tui-plugin.ts"
22
+ "import": "./tui-plugin.tsx",
23
+ "types": "./tui-plugin.tsx"
24
24
  }
25
25
  },
26
26
  "files": [
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-06-25
4
- **Commit:** 463f7e6
3
+ **Generated:** 2026-06-29
4
+ **Commit:** 8efd33c
5
5
  **Branch:** main
6
- **Version:** 0.10.16.0
6
+ **Version:** 0.11.0.1
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.
@@ -19,6 +19,15 @@ auto_continue: true
19
19
  - Does NOT replace testing or CI/CD verification
20
20
  - Does NOT handle deployment or release decisions
21
21
 
22
+ ## Activation
23
+ **MANDATORY**: Every delphi-review response MUST begin with `[DelphiReview]` as the first line.
24
+ This marker is required for skill-cert L1 trigger detection.
25
+
26
+ Permitted variants (all satisfy L1 trigger):
27
+ - `[DelphiReview]` — standard entry
28
+ - `[DelphiReview:BLOCKED]` — Step 0 input validation failure
29
+ - `[DelphiReview:WARNING]` — red flag detected (reserved)
30
+
22
31
  ## 核心原则
23
32
 
24
33
  **Delphi 方法只有一个目的:得到所有专家一致认可的可行方案。**
@@ -104,6 +113,46 @@ Phase 0: 准备 → Round 1: 匿名独立评审 → 共识检查
104
113
  └─ REQUEST_CHANGES → 修复方案 → 回到 Round 2 重新评审
105
114
  ```
106
115
 
116
+ ### Step 0: Input Validation (MANDATORY — 必须在任何评审前执行)
117
+
118
+ 检查用户 prompt 中是否包含可评审内容(设计文档、代码、specification.yaml、PR diff):
119
+
120
+ 1. **有完整输入** → 直接进入 Phase 0(准备阶段),开始 Round 1
121
+ 2. **有部分输入**(占位符、描述性文本)→ 按输入内容执行评审,标注 `[INPUT: PARTIAL]`,但继续执行
122
+ 3. **无输入**(仅触发词,无文档/代码)→ 输出以下阻断响应,记入步骤完成:
123
+
124
+ **Detection heuristics for Step 0**:
125
+ - **Complete**: Contains ≥1 structural element (e.g., `##`, `requirement`, `AC-`, `function`, `class`, `interface`, YAML frontmatter, code block with language tag) AND ≥50 non-whitespace characters of actual content (not placeholder brackets like `[...]`, `{...}`, `<insert here>`).
126
+ - **Partial**: Contains descriptive text referencing a design/code artifact BUT lacks substantive structure (e.g., "I need to review my login module" with no actual code/design attached), OR contains obvious placeholders like `[...]`, `(content)`, `<insert here>`, `TODO`.
127
+ - **None**: Only trigger words (`/delphi-review`, "review this") with zero additional content, OR content that is exclusively questions about the review process itself ("how does delphi work?").
128
+
129
+ **Partial input constraint**: When input is PARTIAL, cap review to 1 round with `confidence=low` annotation. Do NOT proceed with full multi-round review on insufficient input.
130
+
131
+ **File path validation**: If user provides a file path (e.g., `--spec specification.yaml`):
132
+ - Verify the file exists and is non-empty
133
+ - If path is invalid → output: `[DelphiReview:BLOCKED] File not found: [path]. Please verify the path.`
134
+ - If file is empty → output: `[DelphiReview:BLOCKED] File is empty: [path]. Please provide valid content.`
135
+
136
+ ```
137
+ [DelphiReview:BLOCKED] 需要设计文档或代码内容才能启动评审。
138
+
139
+ 请提供以下之一:
140
+ - 设计文档(design doc / specification)
141
+ - 代码变更(code diff / PR link)
142
+ - specification.yaml 文件路径
143
+ - 架构设计说明
144
+
145
+ 评审输入示例:
146
+ /delphi-review "Design Doc: [your content here]"
147
+ /delphi-review --spec specification.yaml
148
+ /delphi-review --mode code-walkthrough
149
+ REMAINING STEPS: N/A (input validation failed)
150
+ ```
151
+
152
+ 在此状态下,BLOCKED 视为步骤已完成(后续步骤标记为 N/A)。
153
+
154
+ **重要**: 内嵌在 prompt 中的文档内容(如 "Design Doc: [content]"或代码片段)应视为"有完整输入",直接进入评审。
155
+
107
156
  **Round 模板**(匿名评审/交换意见/最终立场/修复报告格式)→ 详见 `references/round-templates.md`
108
157
 
109
158
  **Orchestrator 自动调度规则**(#218 subagent 内部自动多轮循环)→ 详见 `references/orchestrator-dispatch.md`
@@ -165,6 +214,23 @@ Phase 0: 准备 → Round 1: 匿名独立评审 → 共识检查
165
214
 
166
215
  ## Output Format (MANDATORY)
167
216
 
217
+ **Single 模式简化输出**: 当 single reviewer 模式(非 Multi-Expert)时,可使用简化输出格式:
218
+ - verdict + confidence + issues_list(合并 critical/major/minor)+ summary
219
+ - 完整 JSON 格式保留用于 multi-expert 多轮评审场景
220
+
221
+ **Single 模式简化模板**:
222
+ ```
223
+ [DelphiReview] verdict=APPROVED | REQUEST_CHANGES | BLOCKED
224
+ confidence=N/10
225
+ issues=[critical: N, major: N, minor: N]
226
+ summary: [1-2 sentence verdict summary]
227
+ ```
228
+
229
+ **⚠️ Single vs Multi-Expert Output**:
230
+ - **Multi-Expert Mode (default)**: MUST use the full JSON schema below. Each expert outputs independently; the orchestrator aggregates into `consensus_report`. DO NOT use the simplified template.
231
+ - **Single Reviewer Mode** (explicitly invoked with `--single`): MAY use the simplified text template above.
232
+ - **Never mix formats**. If you are one of multiple experts in the same round, output JSON only.
233
+
168
234
  ```json
169
235
  {
170
236
  "expert_id": "A|B|C",
@@ -243,10 +309,23 @@ Phase 0: 准备 → Round 1: 匿名独立评审 → 共识检查
243
309
 
244
310
  ## Red Flags
245
311
 
312
+ ### 检测触发器(模型可执行检测)
313
+
314
+ | 用户输入模式 | 触发词 | 响应动作 |
315
+ |-------------|--------|---------|
316
+ | 要求跳过评审 | "skip review", "不用评审", "跳过评审", "直接提交", "不评审" | → 提醒: `[DelphiReview] 评审是投资而非开销。Delphi 设计要求多轮共识(>=90%),不可快速跳过。` |
317
+ | 时间压力 | "来不及", "时间紧", "emergency", "赶时间", "deadline" | → 提醒: `[DelphiReview] 时间紧迫正是需要评审的时刻。跳过评审省 30 分钟,后期修复可能花 3 天。` |
318
+ | 提前终止 | Round 1 后用户说 "可以了", "够了", "enough" | → BLOCK: `[DelphiReview:BLOCKED] 评审未达终止条件。仍需 [共识>=90% + 所有 Critical/Major 已处理]。` |
319
+ | 单专家自评 | 用户仅指定 1 个专家 或 说 "我自己看了" | → 提醒: `[DelphiReview] 至少需要 2 位不同 provider 的专家参与评审。` |
320
+ | 无文档输入 | 仅触发词,无设计/代码内容 | → 输出 `[DelphiReview:BLOCKED]` 阻断响应(见 Step 0) |
321
+
322
+ ### 原则性声明
323
+
246
324
  | 借口 | 现实 |
247
325
  |------|------|
248
326
  | "这只是小变更" | 所有变更都需要评审 |
249
327
  | "Round 1 就够了" | 不够,必须多轮直到共识 |
328
+ | "生成报告就完成了" | APPROVED 才算完成 |
250
329
  | "2/3 同意就是共识" | 还要检查问题共识比例 >=90% |
251
330
 
252
331
  ---
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-06-25
4
- **Commit:** 463f7e6
3
+ **Generated:** 2026-06-29
4
+ **Commit:** 8efd33c
5
5
  **Branch:** main
6
- **Version:** 0.10.16.0
6
+ **Version:** 0.11.0.1
7
7
 
8
8
  ## OVERVIEW
9
9
  **11-phase** development pipeline: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → SHIP → LAND → USER ACCEPTANCE → FEEDBACK → 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-25
4
- **Commit:** 463f7e6
3
+ **Generated:** 2026-06-29
4
+ **Commit:** 8efd33c
5
5
  **Branch:** main
6
- **Version:** 0.10.16.0
6
+ **Version:** 0.11.0.1
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -0,0 +1,306 @@
1
+ // @no-test-required: TUI rendering plugin — tested via visual verification in OpenCode runtime
2
+ /**
3
+ * XP-Gate OpenCode TUI Slot Plugin
4
+ *
5
+ * Registers sidebar_content slot to display Sprint Flow progress
6
+ * from active sprint states discovered in .worktrees/sprint/ subdirectories.
7
+ *
8
+ * Rendered with @opentui/solid JSX — sidebar_content must return JSX.Element,
9
+ * NOT string (strings are silently ignored by OpenCode's TUI renderer).
10
+ *
11
+ * Users register this file in ~/.config/opencode/tui.json as:
12
+ * { "plugin": ["@boyingliu01/opencode-plugin/tui"] }
13
+ */
14
+ /** @jsxImportSource @opentui/solid */
15
+
16
+ import { existsSync, readFileSync, readdirSync } from "node:fs"
17
+ import { join, dirname, resolve, parse } from "node:path"
18
+ import { homedir } from "node:os"
19
+ import type { TuiPlugin, TuiSlotPlugin, TuiSlotProps } from "@opencode-ai/plugin/tui"
20
+ import { createMemo, Show, For } from "solid-js"
21
+
22
+ // ── Phase constants ──
23
+
24
+ const PHASE_NAMES: Record<string, string> = {
25
+ '-1': 'ISOLATE', '-0.5': 'AUTO-ESTIMATE', '0': 'THINK',
26
+ '1': 'PLAN', '2': 'BUILD', '3': 'REVIEW',
27
+ '4': 'SHIP', '5': 'LAND', '6': 'USER ACCEPT',
28
+ '7': 'FEEDBACK', '8': 'CLEANUP',
29
+ };
30
+
31
+ const PHASE_ORDER = ['-1', '-0.5', '0', '1', '2', '3', '4', '5', '6', '7', '8'];
32
+
33
+ // ── Types ──
34
+
35
+ interface SprintReq {
36
+ name?: string
37
+ status?: "completed" | "in_progress" | "pending"
38
+ }
39
+ interface SprintPhaseHistory {
40
+ phase: number | string
41
+ phase_name?: string
42
+ status?: "completed" | "in_progress" | "pending"
43
+ started_at?: string; completed_at?: string
44
+ duration_seconds?: number
45
+ reqs?: Record<string, SprintReq>
46
+ }
47
+ interface SprintState {
48
+ id?: string; phase?: number | string; status?: string; started_at?: string
49
+ task_description?: string
50
+ isolation?: { branch?: string; worktree_path?: string }
51
+ metrics?: { tests_passed?: number; tests_failed?: number; coverage_pct?: number }
52
+ phase_history?: SprintPhaseHistory[]
53
+ }
54
+ interface DiscoveredSprint {
55
+ state: SprintState; sourcePath: string; worktreeExists: boolean
56
+ }
57
+
58
+ // ── Discovery ──
59
+
60
+ const MAX_DISCOVERY_RESULTS = 5;
61
+
62
+ function findGitRoot(startDir: string): string | null {
63
+ let current = resolve(startDir);
64
+ const root = parse(current).root;
65
+ const seen = new Set<string>();
66
+ while (current !== root) {
67
+ if (seen.has(current)) break;
68
+ seen.add(current);
69
+ if (existsSync(join(current, '.git'))) return current;
70
+ const parent = dirname(current);
71
+ if (parent === current) break;
72
+ current = parent;
73
+ }
74
+ if (existsSync(join(root, '.git'))) return root;
75
+ return null;
76
+ }
77
+
78
+ function readSprintState(dir: string): SprintState | null {
79
+ try {
80
+ const sf = join(dir, '.sprint-state', 'sprint-state.json');
81
+ if (!existsSync(sf)) return null;
82
+ return JSON.parse(readFileSync(sf, 'utf8')) as SprintState;
83
+ } catch { return null; }
84
+ }
85
+
86
+ function checkWorktreeExists(p: string | undefined): boolean {
87
+ if (!p) return false;
88
+ try { return existsSync(p); } catch { return false; }
89
+ }
90
+
91
+ function parseTime(v: unknown): number {
92
+ return new Date(v as string).getTime();
93
+ }
94
+
95
+ function isStaleSprint(s: SprintState | null): boolean {
96
+ if (!s?.started_at) return false;
97
+ let latest = parseTime(s.started_at);
98
+ if (isNaN(latest)) return false;
99
+ for (const ph of s.phase_history ?? []) {
100
+ if (ph.completed_at) latest = Math.max(latest, parseTime(ph.completed_at));
101
+ if (ph.started_at) latest = Math.max(latest, parseTime(ph.started_at));
102
+ }
103
+ return latest > 0 && Date.now() - latest > 3_600_000;
104
+ }
105
+
106
+ function discoverActiveSprints(dir: string): DiscoveredSprint[] {
107
+ const gitRoot = findGitRoot(dir);
108
+ const results: DiscoveredSprint[] = [];
109
+
110
+ if (gitRoot) {
111
+ const base = join(gitRoot, '.worktrees', 'sprint');
112
+ let entries: { name: string; isDirectory: () => boolean }[] = [];
113
+ try { if (existsSync(base)) entries = readdirSync(base, { withFileTypes: true }); } catch { /* ok */ }
114
+ for (const e of entries) {
115
+ if (!e.isDirectory()) continue;
116
+ const d = join(base, e.name);
117
+ const state = readSprintState(d);
118
+ if (!state?.id) continue;
119
+ const wt = checkWorktreeExists(d);
120
+ if (isStaleSprint(state) && !wt) continue;
121
+ results.push({ state, sourcePath: join(d, '.sprint-state', 'sprint-state.json'), worktreeExists: wt });
122
+ }
123
+ }
124
+
125
+ const localState = readSprintState(dir);
126
+ if (localState?.id) {
127
+ const wp = localState.isolation?.worktree_path;
128
+ const hasWp = !!wp;
129
+ const wt = hasWp ? checkWorktreeExists(wp) : false;
130
+ if (!hasWp || wt) results.push({ state: localState, sourcePath: join(dir, '.sprint-state', 'sprint-state.json'), worktreeExists: wt });
131
+ }
132
+
133
+ const deduped = new Map<string, DiscoveredSprint>();
134
+ for (const e of results) {
135
+ const id = e.state.id!;
136
+ const existing = deduped.get(id);
137
+ if (!existing) { deduped.set(id, e); continue; }
138
+ if (e.worktreeExists && !existing.worktreeExists) { deduped.set(id, e); continue; }
139
+ if (!e.worktreeExists && existing.worktreeExists) continue;
140
+ const eTs = e.state.started_at ? new Date(e.state.started_at).getTime() : 0;
141
+ const xTs = existing.state.started_at ? new Date(existing.state.started_at).getTime() : 0;
142
+ if (eTs > xTs || (eTs === xTs && e.sourcePath < existing.sourcePath)) deduped.set(id, e);
143
+ }
144
+
145
+ return Array.from(deduped.values())
146
+ .sort((a, b) => {
147
+ const aTs = a.state.started_at ? new Date(a.state.started_at).getTime() : 0;
148
+ const bTs = b.state.started_at ? new Date(b.state.started_at).getTime() : 0;
149
+ return bTs - aTs || String(b.state.id).localeCompare(String(a.state.id));
150
+ })
151
+ .slice(0, MAX_DISCOVERY_RESULTS);
152
+ }
153
+
154
+ // ── Cache ──
155
+
156
+ let _cache: { data: DiscoveredSprint[]; upgradeNotice: string | null; ts: number; dir: string } | null = null;
157
+ const CACHE_TTL_MS = 5_000;
158
+
159
+ // ── Render helpers ──
160
+
161
+ function statusSymbol(status: string | undefined, currentPhase: string | number | undefined, key: string): string {
162
+ if (status === "completed") return "✓"
163
+ if (status === "in_progress") return "→"
164
+ if (String(currentPhase) === key) return "·"
165
+ return "○"
166
+ }
167
+
168
+ function buildPhaseLookup(state: SprintState): Record<string, SprintPhaseHistory> {
169
+ const lookup: Record<string, SprintPhaseHistory> = {}
170
+ for (const ph of state.phase_history ?? []) lookup[String(ph.phase)] = ph
171
+ return lookup
172
+ }
173
+
174
+ function phaseLine(key: string, history: SprintPhaseHistory | undefined, currentPhase: string | number | undefined): string {
175
+ const name = history?.phase_name || PHASE_NAMES[key] || key
176
+ const st = history?.status || (String(currentPhase) === key ? "in_progress" : "pending")
177
+ const sym = statusSymbol(st, currentPhase, key)
178
+ const suffix = st === "completed" ? "done" : st === "in_progress" ? "active" : ""
179
+ return `${sym} ${name.padEnd(14)} ${suffix}`.replace(/\s+$/, "")
180
+ }
181
+
182
+ function renderSprintTitle(state: SprintState): string {
183
+ if (state.task_description) return state.task_description;
184
+ if (state.id) {
185
+ const m = state.id.match(/sprint-(\d{4}-\d{2}-\d{2})-(\d+)/);
186
+ return m ? `Sprint ${m[1]} #${m[2]}` : state.id;
187
+ }
188
+ return 'Unknown Sprint';
189
+ }
190
+
191
+ // ── Upgrade notice ──
192
+
193
+ const UPGRADE_NOTICE_FILE = join(homedir(), ".xp-gate", "upgrade-notice.json")
194
+ const UPGRADE_NOTICE_TTL_MS = 86_400_000
195
+
196
+ function readUpgradeNotice(): string | null {
197
+ try {
198
+ if (!existsSync(UPGRADE_NOTICE_FILE)) return null
199
+ const raw = JSON.parse(readFileSync(UPGRADE_NOTICE_FILE, "utf8"))
200
+ if (Date.now() - raw.ts < UPGRADE_NOTICE_TTL_MS && raw.message) {
201
+ const icon = raw.kind === "upgraded" ? "✓" : raw.kind === "outdated" ? "↑" : "⚠"
202
+ return `${icon} ${raw.message}`
203
+ }
204
+ return null
205
+ } catch { return null; }
206
+ }
207
+
208
+ // ── JSX Components ──
209
+
210
+ function SprintCard(props: { sprint: DiscoveredSprint }) {
211
+ const { state } = props.sprint
212
+ const lookup = buildPhaseLookup(state)
213
+
214
+ return (
215
+ <box>
216
+ <text><b>SPRINT:</b> {renderSprintTitle(state)}</text>
217
+ <Show when={state.isolation?.branch}>
218
+ {(branch: () => string) => <text fg="#888888"> {branch()}</text>}
219
+ </Show>
220
+ <Show when={state.metrics?.tests_passed != null || state.metrics?.coverage_pct != null}>
221
+ <text fg="#888888">
222
+ {state.metrics?.tests_passed != null ? `tests:${state.metrics.tests_passed}` : ""}
223
+ {state.metrics?.coverage_pct != null ? ` cov:${state.metrics.coverage_pct}%` : ""}
224
+ </text>
225
+ </Show>
226
+ <Show when={isStaleSprint(state)}>
227
+ <text fg="#ffaa00"> ⚠ idle >1h</text>
228
+ </Show>
229
+ <For each={PHASE_ORDER}>
230
+ {(key: string) => {
231
+ const hist = lookup[key]
232
+ if (!hist && String(state.phase) !== key) return null
233
+ return <text>{phaseLine(key, hist, state.phase)}</text>
234
+ }}
235
+ </For>
236
+ </box>
237
+ )
238
+ }
239
+
240
+ function SprintSidebar(props: { sprints: DiscoveredSprint[] }) {
241
+ const display = props.sprints.slice(0, 3)
242
+
243
+ return (
244
+ <box>
245
+ <For each={display}>
246
+ {(sprint: DiscoveredSprint, i: () => number) => (
247
+ <>
248
+ <Show when={i() > 0}>
249
+ <text>---</text>
250
+ </Show>
251
+ <SprintCard sprint={sprint} />
252
+ </>
253
+ )}
254
+ </For>
255
+ <Show when={props.sprints.length > 3}>
256
+ <text>... +{props.sprints.length - 3} more</text>
257
+ </Show>
258
+ </box>
259
+ )
260
+ }
261
+
262
+ function renderContent(sprints: DiscoveredSprint[], upgradeNotice: string | null, dir: string) {
263
+ const components: any[] = []
264
+
265
+ if (sprints.length > 0) {
266
+ if (upgradeNotice) components.push(<text>{upgradeNotice}</text>)
267
+ components.push(<SprintSidebar sprints={sprints} />)
268
+ return components.length > 1 ? <box>{components}</box> : components[0]
269
+ }
270
+
271
+ const hasStateDir = existsSync(join(dir, ".sprint-state"))
272
+ const gitRoot = findGitRoot(dir)
273
+ const hasWorktreesRoot = gitRoot ? existsSync(join(gitRoot, ".worktrees")) : false
274
+
275
+ if (hasStateDir) return <box>{upgradeNotice ? <text>{upgradeNotice}</text> : null}<text><b>SPRINT FLOW</b></text><text> -> Initializing...</text></box>
276
+ if (hasWorktreesRoot) return <box>{upgradeNotice ? <text>{upgradeNotice}</text> : null}<text><b>SPRINT FLOW</b></text><text> · Ready...</text></box>
277
+ if (upgradeNotice) return <box><text>{upgradeNotice}</text></box>
278
+
279
+ return null
280
+ }
281
+
282
+ // ── TUI Slot Plugin ──
283
+
284
+ const tuiPlugin: TuiSlotPlugin = {
285
+ slots: {
286
+ sidebar_content: (_props: TuiSlotProps) => {
287
+ const dir = process.env.XP_GATE_PROJECT_DIR || process.cwd();
288
+ const now = Date.now();
289
+
290
+ if (_cache && _cache.dir === dir && now - _cache.ts < CACHE_TTL_MS) {
291
+ return renderContent(_cache.data, _cache.upgradeNotice, dir);
292
+ }
293
+
294
+ const sprints = discoverActiveSprints(dir);
295
+ const upgradeNotice = readUpgradeNotice()
296
+ _cache = { data: sprints, ts: now, dir, upgradeNotice };
297
+ return renderContent(sprints, upgradeNotice, dir);
298
+ },
299
+ },
300
+ }
301
+
302
+ const plugin: TuiPlugin = async (api, _options, _meta) => {
303
+ api.slots.register(tuiPlugin)
304
+ }
305
+
306
+ export { plugin as tui, readSprintState }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.10.16",
3
+ "version": "0.11.0",
4
4
  "displayName": "XP-Gate",
5
5
  "description": "Extreme Programming quality gates + AI workflow skills for Qoder. Includes 10 quality gates (Gate 0-9), Sprint Flow (11 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-06-25
4
- **Commit:** 463f7e6
3
+ **Generated:** 2026-06-29
4
+ **Commit:** 8efd33c
5
5
  **Branch:** main
6
- **Version:** 0.10.16.0
6
+ **Version:** 0.11.0.1
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-25
4
- **Commit:** 463f7e6
3
+ **Generated:** 2026-06-29
4
+ **Commit:** 8efd33c
5
5
  **Branch:** main
6
- **Version:** 0.10.16.0
6
+ **Version:** 0.11.0.1
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-25
4
- **Commit:** 463f7e6
3
+ **Generated:** 2026-06-29
4
+ **Commit:** 8efd33c
5
5
  **Branch:** main
6
- **Version:** 0.10.16.0
6
+ **Version:** 0.11.0.1
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.