@boyingliu01/xp-gate 0.18.2 → 0.18.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.
- package/adapter-common.sh +0 -0
- package/adapters/cpp.sh +0 -0
- package/adapters/dart.sh +0 -0
- package/adapters/flutter.sh +0 -0
- package/adapters/gate-9.sh +1 -0
- package/adapters/go.sh +0 -0
- package/adapters/iac.sh +0 -0
- package/adapters/java.sh +0 -0
- package/adapters/kotlin.sh +0 -0
- package/adapters/objectivec.sh +0 -0
- package/adapters/plugins/book299-20132-python/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-20132-python/scripts/install-maven.sh +0 -0
- package/adapters/plugins/book299-4081-c/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-4081-c/scripts/install-maven.sh +0 -0
- package/adapters/plugins/book299-4083-javascriptes5/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-4083-javascriptes5/scripts/install-maven.sh +0 -0
- package/adapters/powershell.sh +0 -0
- package/adapters/python.sh +0 -0
- package/adapters/shell.sh +0 -0
- package/adapters/swift.sh +0 -0
- package/adapters/typescript.sh +0 -0
- package/bin/xp-gate.js +45 -0
- package/gate-3.sh +0 -0
- package/gate-4.sh +0 -0
- package/gate-7.sh +0 -0
- package/gate-8.sh +0 -0
- package/gate-9.sh +0 -0
- package/hooks/adapter-common.sh +0 -0
- package/hooks/gate-3.sh +0 -0
- package/hooks/gate-4.sh +0 -0
- package/hooks/gate-7.sh +0 -0
- package/hooks/gate-8.sh +0 -0
- package/hooks/gate-9.sh +0 -0
- package/hooks/pre-commit +108 -9
- package/hooks/pre-push +63 -232
- package/hooks/sprint-gate.sh +0 -0
- package/lib/__tests__/install-skill.test.js +3 -3
- package/lib/__tests__/phase-transition.test.js +512 -512
- package/lib/__tests__/sprint-audit.test.js +207 -207
- package/lib/__tests__/test-alignment.test.ts +761 -0
- package/lib/doctor.js +1 -1
- package/lib/install-skill.js +11 -3
- package/lib/language-tools.js +833 -833
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/phase-transition.js +604 -499
- package/lib/sprint-audit.js +235 -235
- package/lib/sprint-init.js +248 -207
- package/lib/test-alignment.ts +651 -0
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/mutation/gate-m.ts +18 -4
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/bin/delphi-review-guard.sh +0 -0
- package/plugins/claude-code/bin/xp-gate-check +0 -0
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/claude-code/skills/grilling/SKILL.md +12 -12
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/opencode/skills/grilling/SKILL.md +12 -12
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/agents/delphi-architecture.md +48 -48
- package/plugins/qoder/agents/delphi-feasibility.md +48 -48
- package/plugins/qoder/agents/delphi-technical.md +48 -48
- package/plugins/qoder/bin/sprint-flow-guard.sh +0 -0
- package/plugins/qoder/bin/xp-gate-check +0 -0
- 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/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/scripts/delphi-external-review.cjs +432 -432
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/grilling/SKILL.md +12 -12
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/test-specification-alignment/AGENTS.md +3 -3
package/lib/sprint-init.js
CHANGED
|
@@ -1,207 +1,248 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* xp-gate sprint-init — Programmatic sprint initialization.
|
|
3
|
-
*
|
|
4
|
-
* Resolves #366 (phase-transition never called by orchestrator).
|
|
5
|
-
* Layer 1 of the three-layer enforcement mechanism.
|
|
6
|
-
*
|
|
7
|
-
* Creates .sprint-state/sprint-state.json as the single entry point
|
|
8
|
-
* for sprint initialization. Replaces manual state file creation.
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* xp-gate sprint-init "<task_description>" [--issues "<#123,#456>"] [--force] [--dry-run] [--dir <path>]
|
|
12
|
-
*
|
|
13
|
-
* Non-interactive design (LLM orchestrator cannot handle stdin):
|
|
14
|
-
* - Default: existing active sprint → error exit 1
|
|
15
|
-
* - --force: override existing sprint, backup old state to .sprint-history/
|
|
16
|
-
* - --dry-run: pre-check conflicts, no writes
|
|
17
|
-
* - Idempotent: same task_description + branch → output dashboard, exit 0
|
|
18
|
-
*
|
|
19
|
-
* @module sprint-init
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
'use strict';
|
|
23
|
-
|
|
24
|
-
const fs = require('fs');
|
|
25
|
-
const path = require('path');
|
|
26
|
-
const { execFileSync } = require('child_process');
|
|
27
|
-
const { SprintStateManager } = require('./sprint-state-manager');
|
|
28
|
-
const { renderDashboard } = require('./phase-transition');
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Detect current git branch.
|
|
32
|
-
* @param {string} cwd - Working directory
|
|
33
|
-
* @returns {string} Branch name or 'detached'
|
|
34
|
-
*/
|
|
35
|
-
function detectBranch(cwd) {
|
|
36
|
-
try {
|
|
37
|
-
const branch = execFileSync('git', ['branch', '--show-current'], {
|
|
38
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
39
|
-
}).trim();
|
|
40
|
-
return branch || 'detached';
|
|
41
|
-
} catch {
|
|
42
|
-
return 'unknown';
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Generate sprint ID from date and sequence.
|
|
48
|
-
* @returns {string} Sprint ID like "sprint-20260722-01"
|
|
49
|
-
*/
|
|
50
|
-
function generateSprintId() {
|
|
51
|
-
const now = new Date();
|
|
52
|
-
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '');
|
|
53
|
-
const seq = String(Math.floor(Math.random() * 99) + 1).padStart(2, '0');
|
|
54
|
-
return `sprint-${dateStr}-${seq}`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Backup existing sprint state to .sprint-history/.
|
|
59
|
-
* @param {string} projectRoot - Project root directory
|
|
60
|
-
* @param {object} state - Current sprint state
|
|
61
|
-
*/
|
|
62
|
-
function backupSprintState(projectRoot, state) {
|
|
63
|
-
const historyDir = path.join(projectRoot, '.sprint-history');
|
|
64
|
-
if (!fs.existsSync(historyDir)) {
|
|
65
|
-
fs.mkdirSync(historyDir, { recursive: true });
|
|
66
|
-
}
|
|
67
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
68
|
-
const backupFile = path.join(historyDir, `sprint-state-${state.id || 'unknown'}-${timestamp}.json`);
|
|
69
|
-
fs.writeFileSync(backupFile, JSON.stringify(state, null, 2), 'utf8');
|
|
70
|
-
return backupFile;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* CLI entry point for sprint-init command.
|
|
75
|
-
* @param {string[]} args - CLI arguments
|
|
76
|
-
* @returns {Promise<number>} Exit code
|
|
77
|
-
*/
|
|
78
|
-
async function handleSprintInit(args = []) {
|
|
79
|
-
if (args.includes('--help')) {
|
|
80
|
-
console.log('Usage: xp-gate sprint-init "<task_description>" [options]');
|
|
81
|
-
console.log('');
|
|
82
|
-
console.log('Arguments:');
|
|
83
|
-
console.log(' task_description Sprint task description (required)');
|
|
84
|
-
console.log('');
|
|
85
|
-
console.log('Options:');
|
|
86
|
-
console.log(' --issues <list> Comma-separated issue numbers (e.g. "#123,#456")');
|
|
87
|
-
console.log(' --force Override existing active sprint (backs up old state)');
|
|
88
|
-
console.log(' --dry-run Pre-check conflicts without writing');
|
|
89
|
-
console.log(' --dir <path> Project directory (default: cwd)');
|
|
90
|
-
console.log('');
|
|
91
|
-
console.log('Examples:');
|
|
92
|
-
console.log(' xp-gate sprint-init "Implement user auth"');
|
|
93
|
-
console.log(' xp-gate sprint-init "Fix #366" --issues "#366" --force');
|
|
94
|
-
return 0;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Parse arguments
|
|
98
|
-
const forceFlag = args.includes('--force');
|
|
99
|
-
const dryRunFlag = args.includes('--dry-run');
|
|
100
|
-
const dirIdx = args.indexOf('--dir');
|
|
101
|
-
const projectDir = dirIdx >= 0 ? args[dirIdx + 1] : process.cwd();
|
|
102
|
-
const issuesIdx = args.indexOf('--issues');
|
|
103
|
-
const issues = issuesIdx >= 0 ? args[issuesIdx + 1] : '';
|
|
104
|
-
|
|
105
|
-
// First non-flag argument is task_description
|
|
106
|
-
const taskDescription = args.find(a => !a.startsWith('--') && a !== projectDir && a !== issues);
|
|
107
|
-
if (!taskDescription) {
|
|
108
|
-
console.error('Error: Missing task_description. Usage: xp-gate sprint-init "<task_description>"');
|
|
109
|
-
return 1;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const manager = new SprintStateManager(projectDir);
|
|
113
|
-
const branch = detectBranch(projectDir);
|
|
114
|
-
|
|
115
|
-
// Check existing sprint state
|
|
116
|
-
let existingState = null;
|
|
117
|
-
try {
|
|
118
|
-
existingState = manager.read();
|
|
119
|
-
} catch {
|
|
120
|
-
// Corrupted state — treat as no state (will be overwritten)
|
|
121
|
-
existingState = null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (existingState) {
|
|
125
|
-
// Idempotent check: same task_description + branch → output dashboard, exit 0
|
|
126
|
-
const normalizedExisting = (existingState.task_description || '').trim();
|
|
127
|
-
const normalizedNew = taskDescription.trim();
|
|
128
|
-
const existingBranch = existingState.isolation?.branch || '';
|
|
129
|
-
|
|
130
|
-
if (normalizedExisting === normalizedNew && existingBranch === branch && branch !== 'detached') {
|
|
131
|
-
console.log('ℹ️ Sprint already initialized (idempotent call). Current state:');
|
|
132
|
-
console.log('');
|
|
133
|
-
console.log(renderDashboard(existingState));
|
|
134
|
-
return 0;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Conflict: existing active sprint
|
|
138
|
-
if (!forceFlag) {
|
|
139
|
-
if (dryRunFlag) {
|
|
140
|
-
console.log(JSON.stringify({
|
|
141
|
-
decision: 'deny',
|
|
142
|
-
reason: `Active sprint "${existingState.id}" exists (task: "${normalizedExisting}")`,
|
|
143
|
-
hint: 'Use --force to override (backs up old state)',
|
|
144
|
-
}));
|
|
145
|
-
return 0;
|
|
146
|
-
}
|
|
147
|
-
console.error(`Error: Active sprint "${existingState.id}" already exists.`);
|
|
148
|
-
console.error(` Task: "${normalizedExisting}"`);
|
|
149
|
-
console.error(` Branch: ${existingBranch}`);
|
|
150
|
-
console.error(' Use --force to override (backs up old state to .sprint-history/)');
|
|
151
|
-
return 1;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// --force: backup and override
|
|
155
|
-
const backupPath = backupSprintState(projectDir, existingState);
|
|
156
|
-
console.log(`📦 Backed up existing sprint to: ${backupPath}`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (dryRunFlag) {
|
|
160
|
-
console.log(JSON.stringify({
|
|
161
|
-
decision: 'allow',
|
|
162
|
-
reason: existingState ? 'Would override existing sprint (--force)' : 'No existing sprint',
|
|
163
|
-
branch,
|
|
164
|
-
task_description: taskDescription.trim(),
|
|
165
|
-
}));
|
|
166
|
-
return 0;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
//
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
1
|
+
/**
|
|
2
|
+
* xp-gate sprint-init — Programmatic sprint initialization.
|
|
3
|
+
*
|
|
4
|
+
* Resolves #366 (phase-transition never called by orchestrator).
|
|
5
|
+
* Layer 1 of the three-layer enforcement mechanism.
|
|
6
|
+
*
|
|
7
|
+
* Creates .sprint-state/sprint-state.json as the single entry point
|
|
8
|
+
* for sprint initialization. Replaces manual state file creation.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* xp-gate sprint-init "<task_description>" [--issues "<#123,#456>"] [--force] [--dry-run] [--dir <path>]
|
|
12
|
+
*
|
|
13
|
+
* Non-interactive design (LLM orchestrator cannot handle stdin):
|
|
14
|
+
* - Default: existing active sprint → error exit 1
|
|
15
|
+
* - --force: override existing sprint, backup old state to .sprint-history/
|
|
16
|
+
* - --dry-run: pre-check conflicts, no writes
|
|
17
|
+
* - Idempotent: same task_description + branch → output dashboard, exit 0
|
|
18
|
+
*
|
|
19
|
+
* @module sprint-init
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
const { execFileSync } = require('child_process');
|
|
27
|
+
const { SprintStateManager } = require('./sprint-state-manager');
|
|
28
|
+
const { renderDashboard } = require('./phase-transition');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Detect current git branch.
|
|
32
|
+
* @param {string} cwd - Working directory
|
|
33
|
+
* @returns {string} Branch name or 'detached'
|
|
34
|
+
*/
|
|
35
|
+
function detectBranch(cwd) {
|
|
36
|
+
try {
|
|
37
|
+
const branch = execFileSync('git', ['branch', '--show-current'], {
|
|
38
|
+
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
39
|
+
}).trim();
|
|
40
|
+
return branch || 'detached';
|
|
41
|
+
} catch {
|
|
42
|
+
return 'unknown';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Generate sprint ID from date and sequence.
|
|
48
|
+
* @returns {string} Sprint ID like "sprint-20260722-01"
|
|
49
|
+
*/
|
|
50
|
+
function generateSprintId() {
|
|
51
|
+
const now = new Date();
|
|
52
|
+
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '');
|
|
53
|
+
const seq = String(Math.floor(Math.random() * 99) + 1).padStart(2, '0');
|
|
54
|
+
return `sprint-${dateStr}-${seq}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Backup existing sprint state to .sprint-history/.
|
|
59
|
+
* @param {string} projectRoot - Project root directory
|
|
60
|
+
* @param {object} state - Current sprint state
|
|
61
|
+
*/
|
|
62
|
+
function backupSprintState(projectRoot, state) {
|
|
63
|
+
const historyDir = path.join(projectRoot, '.sprint-history');
|
|
64
|
+
if (!fs.existsSync(historyDir)) {
|
|
65
|
+
fs.mkdirSync(historyDir, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
68
|
+
const backupFile = path.join(historyDir, `sprint-state-${state.id || 'unknown'}-${timestamp}.json`);
|
|
69
|
+
fs.writeFileSync(backupFile, JSON.stringify(state, null, 2), 'utf8');
|
|
70
|
+
return backupFile;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* CLI entry point for sprint-init command.
|
|
75
|
+
* @param {string[]} args - CLI arguments
|
|
76
|
+
* @returns {Promise<number>} Exit code
|
|
77
|
+
*/
|
|
78
|
+
async function handleSprintInit(args = []) {
|
|
79
|
+
if (args.includes('--help')) {
|
|
80
|
+
console.log('Usage: xp-gate sprint-init "<task_description>" [options]');
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log('Arguments:');
|
|
83
|
+
console.log(' task_description Sprint task description (required)');
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log('Options:');
|
|
86
|
+
console.log(' --issues <list> Comma-separated issue numbers (e.g. "#123,#456")');
|
|
87
|
+
console.log(' --force Override existing active sprint (backs up old state)');
|
|
88
|
+
console.log(' --dry-run Pre-check conflicts without writing');
|
|
89
|
+
console.log(' --dir <path> Project directory (default: cwd)');
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log('Examples:');
|
|
92
|
+
console.log(' xp-gate sprint-init "Implement user auth"');
|
|
93
|
+
console.log(' xp-gate sprint-init "Fix #366" --issues "#366" --force');
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Parse arguments
|
|
98
|
+
const forceFlag = args.includes('--force');
|
|
99
|
+
const dryRunFlag = args.includes('--dry-run');
|
|
100
|
+
const dirIdx = args.indexOf('--dir');
|
|
101
|
+
const projectDir = dirIdx >= 0 ? args[dirIdx + 1] : process.cwd();
|
|
102
|
+
const issuesIdx = args.indexOf('--issues');
|
|
103
|
+
const issues = issuesIdx >= 0 ? args[issuesIdx + 1] : '';
|
|
104
|
+
|
|
105
|
+
// First non-flag argument is task_description
|
|
106
|
+
const taskDescription = args.find(a => !a.startsWith('--') && a !== projectDir && a !== issues);
|
|
107
|
+
if (!taskDescription) {
|
|
108
|
+
console.error('Error: Missing task_description. Usage: xp-gate sprint-init "<task_description>"');
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const manager = new SprintStateManager(projectDir);
|
|
113
|
+
const branch = detectBranch(projectDir);
|
|
114
|
+
|
|
115
|
+
// Check existing sprint state
|
|
116
|
+
let existingState = null;
|
|
117
|
+
try {
|
|
118
|
+
existingState = manager.read();
|
|
119
|
+
} catch {
|
|
120
|
+
// Corrupted state — treat as no state (will be overwritten)
|
|
121
|
+
existingState = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (existingState) {
|
|
125
|
+
// Idempotent check: same task_description + branch → output dashboard, exit 0
|
|
126
|
+
const normalizedExisting = (existingState.task_description || '').trim();
|
|
127
|
+
const normalizedNew = taskDescription.trim();
|
|
128
|
+
const existingBranch = existingState.isolation?.branch || '';
|
|
129
|
+
|
|
130
|
+
if (normalizedExisting === normalizedNew && existingBranch === branch && branch !== 'detached') {
|
|
131
|
+
console.log('ℹ️ Sprint already initialized (idempotent call). Current state:');
|
|
132
|
+
console.log('');
|
|
133
|
+
console.log(renderDashboard(existingState));
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Conflict: existing active sprint
|
|
138
|
+
if (!forceFlag) {
|
|
139
|
+
if (dryRunFlag) {
|
|
140
|
+
console.log(JSON.stringify({
|
|
141
|
+
decision: 'deny',
|
|
142
|
+
reason: `Active sprint "${existingState.id}" exists (task: "${normalizedExisting}")`,
|
|
143
|
+
hint: 'Use --force to override (backs up old state)',
|
|
144
|
+
}));
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
console.error(`Error: Active sprint "${existingState.id}" already exists.`);
|
|
148
|
+
console.error(` Task: "${normalizedExisting}"`);
|
|
149
|
+
console.error(` Branch: ${existingBranch}`);
|
|
150
|
+
console.error(' Use --force to override (backs up old state to .sprint-history/)');
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// --force: backup and override
|
|
155
|
+
const backupPath = backupSprintState(projectDir, existingState);
|
|
156
|
+
console.log(`📦 Backed up existing sprint to: ${backupPath}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (dryRunFlag) {
|
|
160
|
+
console.log(JSON.stringify({
|
|
161
|
+
decision: 'allow',
|
|
162
|
+
reason: existingState ? 'Would override existing sprint (--force)' : 'No existing sprint',
|
|
163
|
+
branch,
|
|
164
|
+
task_description: taskDescription.trim(),
|
|
165
|
+
}));
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Read previous sprint retro data for auto-population (Phase 6 / D7)
|
|
170
|
+
const historyDir = path.join(projectDir, '.sprint-history');
|
|
171
|
+
let previousRetro = null;
|
|
172
|
+
if (fs.existsSync(historyDir)) {
|
|
173
|
+
const entries = fs.readdirSync(historyDir).filter(f => f.startsWith('sprint-')).sort().reverse();
|
|
174
|
+
for (const entry of entries) {
|
|
175
|
+
if (entry.endsWith('.json') && !entry.includes('retro')) continue;
|
|
176
|
+
const retroPath = path.join(historyDir, entry);
|
|
177
|
+
if (fs.existsSync(retroPath)) {
|
|
178
|
+
try {
|
|
179
|
+
const raw = fs.readFileSync(retroPath, 'utf8');
|
|
180
|
+
previousRetro = JSON.parse(raw);
|
|
181
|
+
break;
|
|
182
|
+
} catch {
|
|
183
|
+
// Skip unparseable files
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
break; // Only check the most recent sprint
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Create new sprint state
|
|
191
|
+
const sprintId = generateSprintId();
|
|
192
|
+
const newState = {
|
|
193
|
+
_schema_version: 1,
|
|
194
|
+
evidence_schema_version: 2, // Phase 5: new sprints default to full BLOCK enforcement
|
|
195
|
+
id: sprintId,
|
|
196
|
+
task_description: taskDescription.trim(),
|
|
197
|
+
phase: 1,
|
|
198
|
+
status: 'in_progress',
|
|
199
|
+
started_at: new Date().toISOString(),
|
|
200
|
+
phase_history: [
|
|
201
|
+
{
|
|
202
|
+
phase: 1,
|
|
203
|
+
name: 'PREP',
|
|
204
|
+
status: 'in_progress',
|
|
205
|
+
started_at: new Date().toISOString(),
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
isolation: {
|
|
209
|
+
worktree_path: projectDir,
|
|
210
|
+
branch,
|
|
211
|
+
},
|
|
212
|
+
outputs: issues ? { issues } : {},
|
|
213
|
+
metrics: {},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Phase 6 / D7: Auto-populate from previous sprint retro data
|
|
217
|
+
if (previousRetro) {
|
|
218
|
+
if (previousRetro.rework_rate != null) {
|
|
219
|
+
newState.metrics.previous_rework_rate = previousRetro.rework_rate;
|
|
220
|
+
}
|
|
221
|
+
if (previousRetro.evidence_skips != null) {
|
|
222
|
+
newState.metrics.previous_evidence_skips = previousRetro.evidence_skips;
|
|
223
|
+
}
|
|
224
|
+
if (previousRetro.duration_seconds != null) {
|
|
225
|
+
newState.metrics.previous_duration_seconds = previousRetro.duration_seconds;
|
|
226
|
+
}
|
|
227
|
+
if (Object.keys(newState.metrics).length > 0) {
|
|
228
|
+
console.log('📊 Auto-populated from previous sprint retro data:');
|
|
229
|
+
for (const [key, value] of Object.entries(newState.metrics)) {
|
|
230
|
+
console.log(` ${key}: ${value}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Write state atomically
|
|
236
|
+
manager.write(newState);
|
|
237
|
+
|
|
238
|
+
console.log(`✅ Sprint initialized: ${sprintId}`);
|
|
239
|
+
console.log(` Task: ${taskDescription.trim()}`);
|
|
240
|
+
console.log(` Branch: ${branch}`);
|
|
241
|
+
if (issues) console.log(` Issues: ${issues}`);
|
|
242
|
+
console.log('');
|
|
243
|
+
console.log(renderDashboard(newState));
|
|
244
|
+
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
module.exports = { handleSprintInit, detectBranch, generateSprintId, backupSprintState };
|