@boyingliu01/xp-gate 0.12.2 → 0.12.4

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 (54) hide show
  1. package/adapters/cpp.sh +0 -0
  2. package/adapters/dart.sh +0 -0
  3. package/adapters/flutter.sh +0 -0
  4. package/adapters/go.sh +0 -0
  5. package/adapters/java.sh +0 -0
  6. package/adapters/kotlin.sh +0 -0
  7. package/adapters/objectivec.sh +0 -0
  8. package/adapters/powershell.sh +0 -0
  9. package/adapters/python.sh +0 -0
  10. package/adapters/shell.sh +0 -0
  11. package/adapters/swift.sh +0 -0
  12. package/adapters/typescript.sh +0 -0
  13. package/bin/xp-gate.js +19 -5
  14. package/gate-3.sh +0 -0
  15. package/gate-4.sh +0 -0
  16. package/gate-7.sh +0 -0
  17. package/gate-8.sh +0 -0
  18. package/gate-9.sh +0 -0
  19. package/lib/__tests__/update-hooks.test.js +573 -0
  20. package/lib/init.js +9 -6
  21. package/lib/sprint-status.js +35 -1
  22. package/lib/update-hooks.js +288 -0
  23. package/mock-policy/AGENTS.md +3 -3
  24. package/mutation/AGENTS.md +3 -3
  25. package/package.json +1 -1
  26. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  27. package/plugins/claude-code/skills/delphi-review/AGENTS.md +6 -6
  28. package/plugins/claude-code/skills/delphi-review/SKILL.md +34 -12
  29. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
  30. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  31. package/plugins/opencode/package.json +1 -1
  32. package/plugins/opencode/skills/delphi-review/AGENTS.md +6 -6
  33. package/plugins/opencode/skills/delphi-review/SKILL.md +34 -12
  34. package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
  35. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  36. package/plugins/qoder/plugin.json +1 -1
  37. package/plugins/qoder/skills/delphi-review/AGENTS.md +6 -6
  38. package/plugins/qoder/skills/delphi-review/SKILL.md +198 -264
  39. package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
  40. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  41. package/principles/AGENTS.md +3 -3
  42. package/skills/delphi-review/AGENTS.md +6 -6
  43. package/skills/delphi-review/SKILL.md +34 -12
  44. package/skills/sprint-flow/AGENTS.md +3 -3
  45. package/skills/test-specification-alignment/AGENTS.md +3 -3
  46. package/lib/__tests__/next-sprint.test.js +0 -231
  47. package/lib/next-sprint.js +0 -129
  48. package/lib/shared-phase-constants.d.ts +0 -17
  49. package/lib/shared-phase-constants.js +0 -77
  50. package/plugins/opencode/__tests__/tui-plugin.test.ts +0 -543
  51. package/plugins/opencode/__tests__/xp-gate-e2e-upgrade.test.ts +0 -562
  52. package/plugins/opencode/__tests__/xp-gate-update.test.ts +0 -518
  53. package/plugins/opencode/tui-plugin.ts +0 -414
  54. package/plugins/opencode/tui-plugin.tsx +0 -306
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-01
4
- **Commit:** 58ec6df
3
+ **Generated:** 2026-07-03
4
+ **Commit:** c3ed581
5
5
  **Branch:** main
6
- **Version:** 0.12.2.0
6
+ **Version:** 0.12.4.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.
@@ -32,9 +32,9 @@ skills/delphi-review/
32
32
  - 3 experts anonymous in Round 1 (no cross-expert bias)
33
33
  - ≥90% consensus threshold (was 95%, now unified to 90%)
34
34
  - Max 5 rounds before forcing decision
35
- - Cross-provider required: experts from ≥2 different providers
36
- - Domestic models only: glm, kimi, minimax, qwen, deepseek
37
- - Foreign models forbidden: Anthropic, OpenAI, Google
35
+ - Cross-provider required: experts from ≥2 different providers (model list read from `opencode.json` agent config, not hardcoded)
36
+ - Model selection: reads `delphi-reviewer-*` agent `model` fields from `opencode.json`
37
+ - No hardcoded model lists — models defined by user's `opencode.json` configuration
38
38
  - Code-walkthrough mode: triggered on git push, stores result in .code-walkthrough-result.json
39
39
  - Code-walkthrough skipped on main/master pushes (by design)
40
40
 
@@ -88,22 +88,44 @@ Permitted variants (all satisfy L1 trigger):
88
88
  | 2 专家(默认) | A(架构) + B(实现) | 代码变更、小型设计 |
89
89
  | 3 专家 | A(架构) + B(实现) + C(可行性) | 架构决策、需求文档 |
90
90
 
91
- ### 模型选择策略(强制)
91
+ ### 模型选择策略(强制 — 从 opencode.json 读取)
92
92
 
93
- **MUST 使用国产开源模型**,**严禁** Anthropic/GPT/Gemini 等国外模型。
93
+ **MUST `opencode.json` 的 agent 配置中读取模型**,**严禁** hardcode 模型名称。
94
94
 
95
- | 厂家 | 可用模型 |
96
- |------|---------|
97
- | 深度求索 DeepSeek | `deepseek-v4-pro`, `deepseek-v4-lite` |
98
- | 月之暗面 Kimi | `kimi-k2.6`, `kimi-k2.5` |
99
- | 阿里 Qwen | `qwen3.6-plus`, `qwen3.5-plus` |
100
- | 智谱 GLM | `glm-5.1`, `glm-5.0` |
101
- | MiniMax | `minimax-m2.7`, `minimax-m2.5` |
95
+ 模型选择流程:
96
+ 1. 读取 `opencode.json` 中 `agent` 字段下 `delphi-reviewer-architecture`、`delphi-reviewer-technical`、`delphi-reviewer-feasibility` 三个 agent 定义
97
+ 2. 提取每个 agent `model` 字段(格式:`provider/model-name`)
98
+ 3. 分别作为 Expert A(架构)、Expert B(技术)、Expert C(可行性) 的模型
102
99
 
103
100
  **关键原则**:
104
- - ✅ 三个专家必须来自 **至少 2 家不同厂家**
105
- - ❌ 禁止使用 Anthropic、OpenAI、Google 等国外模型
106
- - ❌ 禁止三个专家全部使用同一厂家模型
101
+ - ✅ 三个专家必须来自 **至少 2 家不同 provider**(通过 `model` 字段的 `provider/` 前缀判断)
102
+ - ❌ 禁止 hardcode 模型名称(模型列表以 `opencode.json` 为准)
103
+ - ❌ 禁止三个专家全部使用同一 provider 的模型
104
+
105
+ **`opencode.json` agent 配置示例**(参考 `opencode.json.delphi.example`):
106
+ ```json
107
+ {
108
+ "agent": {
109
+ "delphi-reviewer-architecture": {
110
+ "description": "...",
111
+ "mode": "subagent",
112
+ "model": "deepseek/deepseek-chat",
113
+ "tools": { "read": true, "bash": true, "write": false, "edit": false }
114
+ },
115
+ "delphi-reviewer-technical": {
116
+ "mode": "subagent",
117
+ "model": "bailian-coding-plan/qwen3.6-plus",
118
+ "tools": { "read": true, "bash": true, "write": false, "edit": false }
119
+ },
120
+ "delphi-reviewer-feasibility": {
121
+ "mode": "subagent",
122
+ "model": "deepseek/deepseek-chat",
123
+ "tools": { "read": true, "bash": true, "write": false, "edit": false }
124
+ }
125
+ }
126
+ }
127
+ ```
128
+ > 上例中 3 个专家使用了 2 个不同 provider(deepseek + bailian-coding-plan),满足跨 provider 要求。**实际模型名称以你的 `opencode.json` 配置为准,无需在 SKILL.md 维护模型列表。**
107
129
 
108
130
  ### 共识阈值
109
131
 
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-01
4
- **Commit:** 58ec6df
3
+ **Generated:** 2026-07-03
4
+ **Commit:** c3ed581
5
5
  **Branch:** main
6
- **Version:** 0.12.2.0
6
+ **Version:** 0.12.4.0
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-07-01
4
- **Commit:** 58ec6df
3
+ **Generated:** 2026-07-03
4
+ **Commit:** c3ed581
5
5
  **Branch:** main
6
- **Version:** 0.12.2.0
6
+ **Version:** 0.12.4.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,231 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
-
5
- describe('next-sprint', () => {
6
- let tmpDir;
7
- let consoleSpy;
8
- let processExitSpy;
9
-
10
- beforeEach(() => {
11
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'next-sprint-test-'));
12
- consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
13
- vi.spyOn(console, 'error').mockImplementation(() => {});
14
- processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {});
15
- // Clear require cache to ensure fresh module for each test
16
- delete require.cache[require.resolve('../next-sprint')];
17
- });
18
-
19
- afterEach(() => {
20
- fs.rmSync(tmpDir, { recursive: true, force: true });
21
- vi.restoreAllMocks();
22
- });
23
-
24
- function createState(overrides = {}) {
25
- const stateDir = path.join(tmpDir, '.sprint-state');
26
- fs.mkdirSync(stateDir, { recursive: true });
27
- const state = {
28
- id: 'sprint-2026-06-30-01',
29
- task_description: 'Test Sprint',
30
- phase: 4,
31
- status: 'completed',
32
- started_at: new Date().toISOString(),
33
- phase_history: [
34
- {
35
- phase: 2,
36
- phase_name: 'BUILD',
37
- status: 'completed',
38
- reqs: {
39
- 'REQ-001': { name: 'Implement login', status: 'completed' },
40
- 'REQ-002': { name: 'Implement logout', status: 'completed' },
41
- },
42
- },
43
- ],
44
- ...overrides,
45
- };
46
- fs.writeFileSync(path.join(stateDir, 'sprint-state.json'), JSON.stringify(state));
47
- return state;
48
- }
49
-
50
- function mockGhCli(issues) {
51
- const { execSync } = require('child_process');
52
- vi.spyOn(require('child_process'), 'execSync').mockImplementation((cmd) => {
53
- if (cmd.includes('gh issue list')) {
54
- return JSON.stringify(issues);
55
- }
56
- return '';
57
- });
58
- }
59
-
60
- describe('readSprintState', () => {
61
- it('returns null when no sprint state exists', () => {
62
- const { readSprintState } = require('../next-sprint');
63
- const result = readSprintState(tmpDir);
64
- expect(result).toBeNull();
65
- });
66
-
67
- it('returns parsed state when sprint state exists', () => {
68
- const state = createState();
69
- const { readSprintState } = require('../next-sprint');
70
- const result = readSprintState(tmpDir);
71
- expect(result).toBeDefined();
72
- expect(result.id).toBe('sprint-2026-06-30-01');
73
- });
74
- });
75
-
76
- describe('getCurrentSprintReqs', () => {
77
- it('extracts requirements from sprint state', () => {
78
- createState();
79
- const { readSprintState, getCurrentSprintReqs } = require('../next-sprint');
80
- const state = readSprintState(tmpDir);
81
- const reqs = getCurrentSprintReqs(state);
82
- expect(reqs).toHaveLength(2);
83
- expect(reqs[0].name).toBe('Implement login');
84
- expect(reqs[1].name).toBe('Implement logout');
85
- });
86
-
87
- it('returns empty array when no phase history', () => {
88
- const { getCurrentSprintReqs } = require('../next-sprint');
89
- const reqs = getCurrentSprintReqs({ phase_history: [] });
90
- expect(reqs).toEqual([]);
91
- });
92
- });
93
-
94
- describe('filterRemainingIssues', () => {
95
- it('filters out issues already in sprint', () => {
96
- const issues = [
97
- { number: 1, title: 'Implement login', labels: [], createdAt: '2026-06-30T00:00:00Z' },
98
- { number: 2, title: 'Implement logout', labels: [], createdAt: '2026-06-30T00:00:00Z' },
99
- { number: 3, title: 'Implement signup', labels: [], createdAt: '2026-06-30T00:00:00Z' },
100
- ];
101
- const sprintReqs = [{ name: 'Implement login' }, { name: 'Implement logout' }];
102
- const { filterRemainingIssues } = require('../next-sprint');
103
- const result = filterRemainingIssues(issues, sprintReqs);
104
- expect(result).toHaveLength(1);
105
- expect(result[0].number).toBe(3);
106
- });
107
-
108
- it('returns all issues when no sprint reqs', () => {
109
- const issues = [
110
- { number: 1, title: 'Implement login', labels: [], createdAt: '2026-06-30T00:00:00Z' },
111
- { number: 2, title: 'Implement logout', labels: [], createdAt: '2026-06-30T00:00:00Z' },
112
- ];
113
- const { filterRemainingIssues } = require('../next-sprint');
114
- const result = filterRemainingIssues(issues, []);
115
- expect(result).toHaveLength(2);
116
- });
117
- });
118
-
119
- describe('formatIssuesTable', () => {
120
- it('formats issues as table', () => {
121
- const issues = [
122
- { number: 1, title: 'Implement login', labels: [{ name: 'feature' }], createdAt: '2026-06-30T00:00:00Z' },
123
- { number: 2, title: 'Implement logout', labels: [{ name: 'bug' }], createdAt: '2026-06-30T00:00:00Z' },
124
- ];
125
- const { formatIssuesTable } = require('../next-sprint');
126
- const result = formatIssuesTable(issues);
127
- expect(result).toContain('Remaining Open Issues');
128
- expect(result).toContain('Implement login');
129
- expect(result).toContain('feature');
130
- });
131
-
132
- it('handles empty issues', () => {
133
- const { formatIssuesTable } = require('../next-sprint');
134
- const result = formatIssuesTable([]);
135
- expect(result).toBe('No remaining issues found.');
136
- });
137
- });
138
-
139
- describe('generateSprintPlan', () => {
140
- it('generates sprint plan for remaining issues', () => {
141
- const issues = [
142
- { number: 1, title: 'Implement login', labels: [], createdAt: '2026-06-30T00:00:00Z' },
143
- { number: 2, title: 'Implement logout', labels: [], createdAt: '2026-06-30T00:00:00Z' },
144
- ];
145
- const { generateSprintPlan } = require('../next-sprint');
146
- const result = generateSprintPlan(issues);
147
- expect(result).toContain('Suggested Next Sprint Plan');
148
- expect(result).toContain('#1');
149
- expect(result).toContain('sprint-flow');
150
- });
151
-
152
- it('returns null when no issues', () => {
153
- const { generateSprintPlan } = require('../next-sprint');
154
- const result = generateSprintPlan([]);
155
- expect(result).toBeNull();
156
- });
157
- });
158
-
159
- describe('handleNextSprint', () => {
160
- it('outputs remaining issues in table format', async () => {
161
- createState();
162
- const { execSync } = require('child_process');
163
- vi.spyOn(require('child_process'), 'execSync').mockImplementation((cmd) => {
164
- if (cmd.includes('gh issue list')) {
165
- return JSON.stringify([
166
- { number: 1, title: 'Implement login', labels: [{ name: 'feature' }], createdAt: '2026-06-30T00:00:00Z' },
167
- { number: 2, title: 'Implement logout', labels: [{ name: 'bug' }], createdAt: '2026-06-30T00:00:00Z' },
168
- { number: 3, title: 'Implement signup', labels: [{ name: 'enhancement' }], createdAt: '2026-06-30T00:00:00Z' },
169
- ]);
170
- }
171
- return '';
172
- });
173
- delete require.cache[require.resolve('../next-sprint')];
174
- const { handleNextSprint } = require('../next-sprint');
175
- const result = await handleNextSprint(['--dir', tmpDir]);
176
- expect(result).toBe(0);
177
- expect(consoleSpy).toHaveBeenCalled();
178
- const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
179
- expect(output).toContain('Remaining Open Issues');
180
- expect(output).toContain('Implement signup');
181
- });
182
-
183
- it('outputs JSON when --json flag', async () => {
184
- createState();
185
- vi.spyOn(require('child_process'), 'execSync').mockImplementation((cmd) => {
186
- if (cmd.includes('gh issue list')) {
187
- return JSON.stringify([
188
- { number: 1, title: 'Implement login', labels: [], createdAt: '2026-06-30T00:00:00Z' },
189
- ]);
190
- }
191
- return '';
192
- });
193
- delete require.cache[require.resolve('../next-sprint')];
194
- const { handleNextSprint } = require('../next-sprint');
195
- const result = await handleNextSprint(['--json', '--dir', tmpDir]);
196
- expect(result).toBe(0);
197
- const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
198
- const parsed = JSON.parse(output);
199
- expect(parsed).toHaveProperty('current_sprint');
200
- expect(parsed).toHaveProperty('remaining_issues');
201
- });
202
-
203
- it('outputs plan when --plan flag', async () => {
204
- createState();
205
- vi.spyOn(require('child_process'), 'execSync').mockImplementation((cmd) => {
206
- if (cmd.includes('gh issue list')) {
207
- return JSON.stringify([
208
- { number: 5, title: 'Implement password reset', labels: [], createdAt: '2026-06-30T00:00:00Z' },
209
- ]);
210
- }
211
- return '';
212
- });
213
- delete require.cache[require.resolve('../next-sprint')];
214
- const { handleNextSprint } = require('../next-sprint');
215
- const result = await handleNextSprint(['--plan', '--dir', tmpDir]);
216
- expect(result).toBe(0);
217
- const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
218
- expect(output).toContain('Suggested Next Sprint Plan');
219
- });
220
-
221
- it('returns 1 when gh CLI not available', async () => {
222
- vi.spyOn(require('child_process'), 'execSync').mockImplementation(() => {
223
- throw new Error('gh: command not found');
224
- });
225
- delete require.cache[require.resolve('../next-sprint')];
226
- const { handleNextSprint } = require('../next-sprint');
227
- const result = await handleNextSprint(['--dir', tmpDir]);
228
- expect(result).toBe(1);
229
- });
230
- });
231
- });
@@ -1,129 +0,0 @@
1
- const childProcess = require('child_process');
2
- const fs = require('fs');
3
- const path = require('path');
4
-
5
- function readSprintState(dir) {
6
- try {
7
- const stateFile = path.join(dir, '.sprint-state', 'sprint-state.json');
8
- if (!fs.existsSync(stateFile)) return null;
9
- return JSON.parse(fs.readFileSync(stateFile, 'utf8'));
10
- } catch {
11
- return null;
12
- }
13
- }
14
-
15
- function getCurrentSprintReqs(state) {
16
- if (!state?.phase_history) return [];
17
- const reqs = [];
18
- for (const phase of state.phase_history) {
19
- if (phase.reqs) {
20
- for (const [id, req] of Object.entries(phase.reqs)) {
21
- reqs.push({ id, name: req.name, status: req.status });
22
- }
23
- }
24
- }
25
- return reqs;
26
- }
27
-
28
- function fetchOpenIssues(repoRoot) {
29
- try {
30
- const result = childProcess.execSync('gh issue list --state open --json number,title,labels,createdAt', {
31
- cwd: repoRoot,
32
- encoding: 'utf8',
33
- timeout: 30000,
34
- });
35
- return JSON.parse(result);
36
- } catch (err) {
37
- if (err.message.includes('gh: command not found')) {
38
- console.error('Error: gh CLI not found. Install it from https://cli.github.com/');
39
- return null;
40
- }
41
- if (err.message.includes('not logged in')) {
42
- console.error('Error: Not logged in to GitHub. Run "gh auth login" first.');
43
- return null;
44
- }
45
- console.error('Error fetching issues:', err.message);
46
- return null;
47
- }
48
- }
49
-
50
- function filterRemainingIssues(issues, sprintReqs) {
51
- const sprintTitles = new Set(sprintReqs.map(r => r.name?.toLowerCase()));
52
- return issues.filter(issue => {
53
- const titleLower = issue.title.toLowerCase();
54
- return !sprintTitles.has(titleLower);
55
- });
56
- }
57
-
58
- function formatIssuesTable(issues) {
59
- if (issues.length === 0) return 'No remaining issues found.';
60
- const lines = ['Remaining Open Issues:', ''];
61
- lines.push('┌──────┬────────────────────────────────────────────┬─────────────────────┬───────────┐');
62
- lines.push('│ # │ Title │ Labels │ Created │');
63
- lines.push('├──────┼────────────────────────────────────────────┼─────────────────────┼───────────┤');
64
- for (const issue of issues) {
65
- const num = String(issue.number).padEnd(4);
66
- const title = issue.title.slice(0, 40).padEnd(40);
67
- const labels = (issue.labels || []).map(l => l.name).join(', ').slice(0, 19).padEnd(19);
68
- const created = issue.createdAt ? issue.createdAt.slice(0, 10) : 'N/A';
69
- lines.push(`│ ${num} │ ${title} │ ${labels} │ ${created} │`);
70
- }
71
- lines.push('└──────┴────────────────────────────────────────────┴─────────────────────┴───────────┘');
72
- lines.push(`Total: ${issues.length} remaining issues`);
73
- return lines.join('\n');
74
- }
75
-
76
- function generateSprintPlan(issues) {
77
- if (issues.length === 0) return null;
78
- const lines = ['Suggested Next Sprint Plan:', ''];
79
- const prioritized = issues.slice(0, 5);
80
- for (let i = 0; i < prioritized.length; i++) {
81
- const issue = prioritized[i];
82
- lines.push(`${i + 1}. #${issue.number} - ${issue.title}`);
83
- }
84
- if (issues.length > 5) {
85
- lines.push(`... and ${issues.length - 5} more issues`);
86
- }
87
- lines.push('');
88
- lines.push('To start a sprint, run:');
89
- lines.push(` /sprint-flow "Implement #${prioritized[0].number}: ${prioritized[0].title}"`);
90
- return lines.join('\n');
91
- }
92
-
93
- async function handleNextSprint(args = []) {
94
- const jsonFlag = args.includes('--json');
95
- const planFlag = args.includes('--plan');
96
- const dirIdx = args.indexOf('--dir');
97
- let repoRoot = process.cwd();
98
- if (dirIdx >= 0 && dirIdx + 1 < args.length) {
99
- repoRoot = path.resolve(args[dirIdx + 1]);
100
- }
101
- const state = readSprintState(repoRoot);
102
- const sprintReqs = state ? getCurrentSprintReqs(state) : [];
103
- const issues = fetchOpenIssues(repoRoot);
104
- if (issues === null) return 1;
105
- const remaining = filterRemainingIssues(issues, sprintReqs);
106
- if (jsonFlag) {
107
- console.log(JSON.stringify({ current_sprint: state?.id || null, sprint_reqs: sprintReqs, remaining_issues: remaining }, null, 2));
108
- return 0;
109
- }
110
- console.log(formatIssuesTable(remaining));
111
- if (planFlag) {
112
- const plan = generateSprintPlan(remaining);
113
- if (plan) {
114
- console.log('');
115
- console.log(plan);
116
- }
117
- }
118
- return 0;
119
- }
120
-
121
- module.exports = {
122
- handleNextSprint,
123
- fetchOpenIssues,
124
- filterRemainingIssues,
125
- formatIssuesTable,
126
- generateSprintPlan,
127
- readSprintState,
128
- getCurrentSprintReqs,
129
- };
@@ -1,17 +0,0 @@
1
- /**
2
- * Type declarations for shared-phase-constants (CommonJS module).
3
- * Consumed by both sprint-status.js (CJS) and tui-plugin.ts (ESM via tsx).
4
- */
5
-
6
- export const PHASE_NAMES: Record<string, string>;
7
- export const PHASE_ORDER: string[];
8
-
9
- /**
10
- * Find the most recent timestamp across started_at and phase_history.
11
- */
12
- export function getLatestTimestamp(state: object): number;
13
-
14
- /**
15
- * Check if the sprint state is stale (>1h since last activity).
16
- */
17
- export function isStale(state: object): boolean;
@@ -1,77 +0,0 @@
1
- /**
2
- * Shared Sprint Flow phase constants.
3
- *
4
- * Single source of truth for PHASE_NAMES and PHASE_ORDER, consumed by both
5
- * the CLI (sprint-status.js, CommonJS) and the OpenCode TUI plugin
6
- * (tui-plugin.ts, ESM via tsx).
7
- *
8
- * @module shared-phase-constants
9
- */
10
-
11
- const PHASE_NAMES = {
12
- '-1': 'ISOLATE',
13
- '-0.5': 'AUTO-ESTIMATE',
14
- '0': 'THINK',
15
- '1': 'PLAN',
16
- '2': 'BUILD',
17
- '3': 'REVIEW',
18
- '4': 'USER ACCEPT',
19
- '5': 'FEEDBACK',
20
- '6': 'SHIP',
21
- '7': 'LAND',
22
- '8': 'CLEANUP',
23
- };
24
-
25
- const PHASE_ORDER = ['-1', '-0.5', '0', '1', '2', '3', '4', '5', '6', '7', '8'];
26
-
27
- /**
28
- * Safely parse a date-like value into epoch ms.
29
- * @param {*} value - Timestamp to parse
30
- * @returns {number} Epoch ms, or NaN if invalid
31
- */
32
- function parseTime(value) {
33
- return new Date(value).getTime();
34
- }
35
-
36
- /**
37
- * Get the max of an existing value and a new timestamp (if valid and larger).
38
- * @param {number} current - Current best value
39
- * @param {*} candidate - Candidate timestamp
40
- * @returns {number} Max value
41
- */
42
- function maxValid(current, candidate) {
43
- if (!candidate) return current;
44
- const t = parseTime(candidate);
45
- return !isNaN(t) && t > current ? t : current;
46
- }
47
-
48
- /**
49
- * Find the most recent timestamp across started_at and phase_history.
50
- * @param {object} state - Sprint state object
51
- * @returns {number} Latest timestamp in ms, or 0 if none found
52
- */
53
- function getLatestTimestamp(state) {
54
- if (!state || !state.started_at) return 0;
55
- const started = parseTime(state.started_at);
56
- if (isNaN(started)) return 0;
57
- let latest = started;
58
- if (Array.isArray(state.phase_history)) {
59
- for (const ph of state.phase_history) {
60
- latest = maxValid(maxValid(latest, ph.completed_at), ph.started_at);
61
- }
62
- }
63
- return latest;
64
- }
65
-
66
- /**
67
- * Check if the sprint state is stale (>1h since last activity).
68
- * @param {object} state - Sprint state object
69
- * @returns {boolean}
70
- */
71
- function isStale(state) {
72
- if (!state || !state.started_at) return false;
73
- const latest = getLatestTimestamp(state);
74
- return latest > 0 && Date.now() - latest > 3600000;
75
- }
76
-
77
- module.exports = { PHASE_NAMES, PHASE_ORDER, getLatestTimestamp, isStale };