@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.
Files changed (79) hide show
  1. package/adapter-common.sh +0 -0
  2. package/adapters/cpp.sh +0 -0
  3. package/adapters/dart.sh +0 -0
  4. package/adapters/flutter.sh +0 -0
  5. package/adapters/gate-9.sh +1 -0
  6. package/adapters/go.sh +0 -0
  7. package/adapters/iac.sh +0 -0
  8. package/adapters/java.sh +0 -0
  9. package/adapters/kotlin.sh +0 -0
  10. package/adapters/objectivec.sh +0 -0
  11. package/adapters/plugins/book299-20132-python/scripts/install-gradle.sh +0 -0
  12. package/adapters/plugins/book299-20132-python/scripts/install-maven.sh +0 -0
  13. package/adapters/plugins/book299-4081-c/scripts/install-gradle.sh +0 -0
  14. package/adapters/plugins/book299-4081-c/scripts/install-maven.sh +0 -0
  15. package/adapters/plugins/book299-4083-javascriptes5/scripts/install-gradle.sh +0 -0
  16. package/adapters/plugins/book299-4083-javascriptes5/scripts/install-maven.sh +0 -0
  17. package/adapters/powershell.sh +0 -0
  18. package/adapters/python.sh +0 -0
  19. package/adapters/shell.sh +0 -0
  20. package/adapters/swift.sh +0 -0
  21. package/adapters/typescript.sh +0 -0
  22. package/bin/xp-gate.js +45 -0
  23. package/gate-3.sh +0 -0
  24. package/gate-4.sh +0 -0
  25. package/gate-7.sh +0 -0
  26. package/gate-8.sh +0 -0
  27. package/gate-9.sh +0 -0
  28. package/hooks/adapter-common.sh +0 -0
  29. package/hooks/gate-3.sh +0 -0
  30. package/hooks/gate-4.sh +0 -0
  31. package/hooks/gate-7.sh +0 -0
  32. package/hooks/gate-8.sh +0 -0
  33. package/hooks/gate-9.sh +0 -0
  34. package/hooks/pre-commit +108 -9
  35. package/hooks/pre-push +63 -232
  36. package/hooks/sprint-gate.sh +0 -0
  37. package/lib/__tests__/install-skill.test.js +3 -3
  38. package/lib/__tests__/phase-transition.test.js +512 -512
  39. package/lib/__tests__/sprint-audit.test.js +207 -207
  40. package/lib/__tests__/test-alignment.test.ts +761 -0
  41. package/lib/doctor.js +1 -1
  42. package/lib/install-skill.js +11 -3
  43. package/lib/language-tools.js +833 -833
  44. package/lib/mattpocock-license-verdict.json +1 -1
  45. package/lib/phase-transition.js +604 -499
  46. package/lib/sprint-audit.js +235 -235
  47. package/lib/sprint-init.js +248 -207
  48. package/lib/test-alignment.ts +651 -0
  49. package/mock-policy/AGENTS.md +3 -3
  50. package/mutation/AGENTS.md +3 -3
  51. package/mutation/gate-m.ts +18 -4
  52. package/package.json +1 -1
  53. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  54. package/plugins/claude-code/bin/delphi-review-guard.sh +0 -0
  55. package/plugins/claude-code/bin/xp-gate-check +0 -0
  56. package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
  57. package/plugins/claude-code/skills/grilling/SKILL.md +12 -12
  58. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
  59. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  60. package/plugins/opencode/package.json +1 -1
  61. package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
  62. package/plugins/opencode/skills/grilling/SKILL.md +12 -12
  63. package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
  64. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  65. package/plugins/qoder/agents/delphi-architecture.md +48 -48
  66. package/plugins/qoder/agents/delphi-feasibility.md +48 -48
  67. package/plugins/qoder/agents/delphi-technical.md +48 -48
  68. package/plugins/qoder/bin/sprint-flow-guard.sh +0 -0
  69. package/plugins/qoder/bin/xp-gate-check +0 -0
  70. package/plugins/qoder/plugin.json +1 -1
  71. package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
  72. package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
  73. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  74. package/principles/AGENTS.md +3 -3
  75. package/scripts/delphi-external-review.cjs +432 -432
  76. package/skills/delphi-review/AGENTS.md +3 -3
  77. package/skills/grilling/SKILL.md +12 -12
  78. package/skills/sprint-flow/AGENTS.md +3 -3
  79. package/skills/test-specification-alignment/AGENTS.md +3 -3
@@ -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
- // Create new sprint state
170
- const sprintId = generateSprintId();
171
- const newState = {
172
- _schema_version: 1,
173
- id: sprintId,
174
- task_description: taskDescription.trim(),
175
- phase: 1,
176
- status: 'in_progress',
177
- started_at: new Date().toISOString(),
178
- phase_history: [
179
- {
180
- phase: 1,
181
- name: 'PREP',
182
- status: 'in_progress',
183
- started_at: new Date().toISOString(),
184
- },
185
- ],
186
- isolation: {
187
- worktree_path: projectDir,
188
- branch,
189
- },
190
- outputs: issues ? { issues } : {},
191
- metrics: {},
192
- };
193
-
194
- // Write state atomically
195
- manager.write(newState);
196
-
197
- console.log(`✅ Sprint initialized: ${sprintId}`);
198
- console.log(` Task: ${taskDescription.trim()}`);
199
- console.log(` Branch: ${branch}`);
200
- if (issues) console.log(` Issues: ${issues}`);
201
- console.log('');
202
- console.log(renderDashboard(newState));
203
-
204
- return 0;
205
- }
206
-
207
- module.exports = { handleSprintInit, detectBranch, generateSprintId, backupSprintState };
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 };