@boyingliu01/xp-gate 0.18.2 → 0.18.3

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 (78) 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/install-skill.js +11 -3
  42. package/lib/language-tools.js +833 -833
  43. package/lib/mattpocock-license-verdict.json +1 -1
  44. package/lib/phase-transition.js +604 -499
  45. package/lib/sprint-audit.js +235 -235
  46. package/lib/sprint-init.js +248 -207
  47. package/lib/test-alignment.ts +651 -0
  48. package/mock-policy/AGENTS.md +3 -3
  49. package/mutation/AGENTS.md +3 -3
  50. package/mutation/gate-m.ts +18 -4
  51. package/package.json +1 -1
  52. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  53. package/plugins/claude-code/bin/delphi-review-guard.sh +0 -0
  54. package/plugins/claude-code/bin/xp-gate-check +0 -0
  55. package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
  56. package/plugins/claude-code/skills/grilling/SKILL.md +12 -12
  57. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
  58. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  59. package/plugins/opencode/package.json +1 -1
  60. package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
  61. package/plugins/opencode/skills/grilling/SKILL.md +12 -12
  62. package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
  63. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  64. package/plugins/qoder/agents/delphi-architecture.md +48 -48
  65. package/plugins/qoder/agents/delphi-feasibility.md +48 -48
  66. package/plugins/qoder/agents/delphi-technical.md +48 -48
  67. package/plugins/qoder/bin/sprint-flow-guard.sh +0 -0
  68. package/plugins/qoder/bin/xp-gate-check +0 -0
  69. package/plugins/qoder/plugin.json +1 -1
  70. package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
  71. package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
  72. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  73. package/principles/AGENTS.md +3 -3
  74. package/scripts/delphi-external-review.cjs +432 -432
  75. package/skills/delphi-review/AGENTS.md +3 -3
  76. package/skills/grilling/SKILL.md +12 -12
  77. package/skills/sprint-flow/AGENTS.md +3 -3
  78. package/skills/test-specification-alignment/AGENTS.md +3 -3
@@ -1,235 +1,235 @@
1
- /**
2
- * xp-gate sprint-audit — Layer 2: Final sprint completeness audit.
3
- *
4
- * Checks phase_history coverage, time records, artifact records,
5
- * and state consistency at Phase 6 CLOSE.
6
- *
7
- * Usage:
8
- * xp-gate sprint-audit [--dir <path>] [--json]
9
- *
10
- * Verdict rules:
11
- * 6/6 completed + 0 errors → PASS (exit 0)
12
- * >= 4/6 completed + 0 errors → PASS_WITH_WARNINGS (exit 0)
13
- * < 4/6 completed or any errors → FAIL (exit 1)
14
- * No sprint-state.json → SKIP (exit 0)
15
- *
16
- * @module sprint-audit
17
- */
18
-
19
- 'use strict';
20
-
21
- const fs = require('fs');
22
- const path = require('path');
23
- const { PHASE_NAMES } = require('./phase-transition');
24
-
25
- /**
26
- * Run the sprint audit.
27
- * @param {string[]} args - CLI arguments
28
- * @returns {Promise<number>} Exit code
29
- */
30
- async function handleSprintAudit(args = []) {
31
- if (args.includes('--help')) {
32
- console.log('Usage: xp-gate sprint-audit [options]');
33
- console.log('');
34
- console.log('Options:');
35
- console.log(' --dir <path> Project directory (default: cwd)');
36
- console.log(' --json Output as JSON instead of human-readable');
37
- console.log('');
38
- console.log('Runs final sprint completeness audit at Phase 6 CLOSE.');
39
- console.log('Checks phase coverage, time records, artifact records,');
40
- console.log('and state consistency.');
41
- return 0;
42
- }
43
-
44
- const dirIdx = args.indexOf('--dir');
45
- const projectDir = dirIdx >= 0 ? args[dirIdx + 1] : process.cwd();
46
- const jsonFlag = args.includes('--json');
47
-
48
- const stateFile = path.join(projectDir, '.sprint-state', 'sprint-state.json');
49
-
50
- // No sprint-state.json → SKIP
51
- if (!fs.existsSync(stateFile)) {
52
- if (jsonFlag) {
53
- console.log(JSON.stringify({ verdict: 'SKIP', reason: 'No sprint-state.json found' }));
54
- } else {
55
- console.log('⏭️ No sprint-state.json found — skipping audit.');
56
- }
57
- return 0;
58
- }
59
-
60
- let state;
61
- try {
62
- state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
63
- } catch (err) {
64
- const result = {
65
- verdict: 'FAIL',
66
- errors: [`Failed to parse sprint-state.json: ${err.message}`],
67
- };
68
- if (jsonFlag) {
69
- console.log(JSON.stringify(result, null, 2));
70
- } else {
71
- console.error(`❌ ${result.errors[0]}`);
72
- }
73
- return 1;
74
- }
75
-
76
- // ── Run checks ──
77
- const warnings = [];
78
- const errors = [];
79
- const phaseHistory = Array.isArray(state.phase_history) ? state.phase_history : [];
80
-
81
- // Build lookup
82
- const historyLookup = {};
83
- for (const entry of phaseHistory) {
84
- historyLookup[entry.phase] = entry;
85
- }
86
-
87
- // Check 1: Phase coverage
88
- const completedPhases = [];
89
- const missingPhases = [];
90
- const incompletePhases = [];
91
-
92
- for (let i = 1; i <= 6; i++) {
93
- const entry = historyLookup[i];
94
- if (!entry) {
95
- missingPhases.push(i);
96
- } else if (entry.status === 'completed' || entry.status === 'skipped') {
97
- completedPhases.push(i);
98
- } else {
99
- incompletePhases.push({ phase: i, name: PHASE_NAMES[i] || `Phase ${i}`, status: entry.status });
100
- }
101
- }
102
-
103
- // Check 2: Time records for completed phases
104
- for (const phaseNum of completedPhases) {
105
- const entry = historyLookup[phaseNum];
106
- if (entry.status === 'completed' && (entry.duration_seconds == null || entry.duration_seconds === 0)) {
107
- warnings.push(`Phase ${phaseNum} (${PHASE_NAMES[phaseNum]}) completed but has no duration recorded`);
108
- }
109
- }
110
-
111
- // Check 3: Artifact records (phase-{N}-summary.md)
112
- const outputsDir = path.join(projectDir, '.sprint-state', 'phase-outputs');
113
- for (const phaseNum of completedPhases) {
114
- if (historyLookup[phaseNum].status === 'completed') {
115
- const summaryFile = path.join(outputsDir, `phase-${phaseNum}-summary.md`);
116
- if (!fs.existsSync(summaryFile)) {
117
- warnings.push(`Phase ${phaseNum} summary missing: .sprint-state/phase-outputs/phase-${phaseNum}-summary.md`);
118
- }
119
- }
120
- }
121
-
122
- // Check 4: State consistency — phase field vs phase_history last entry
123
- if (phaseHistory.length > 0) {
124
- const lastEntry = phaseHistory[phaseHistory.length - 1];
125
- if (state.phase !== undefined && state.phase !== lastEntry.phase) {
126
- errors.push(
127
- `State inconsistency: sprint-state.phase=${state.phase} but phase_history last entry is phase ${lastEntry.phase}`
128
- );
129
- }
130
- }
131
-
132
- // ── Determine verdict ──
133
- const completedCount = completedPhases.length;
134
- let verdict;
135
- if (errors.length > 0 || completedCount < 4) {
136
- verdict = 'FAIL';
137
- } else if (completedCount >= 6 && errors.length === 0) {
138
- verdict = 'PASS';
139
- } else {
140
- verdict = 'PASS_WITH_WARNINGS';
141
- }
142
-
143
- // ── Build result ──
144
- const result = {
145
- sprint_id: state.id || 'unknown',
146
- branch: state.isolation?.branch || 'unknown',
147
- coverage: {
148
- completed: completedCount,
149
- total: 6,
150
- pct: Math.round((completedCount / 6) * 100),
151
- },
152
- missing_phases: missingPhases.map(p => ({ phase: p, name: PHASE_NAMES[p] || `Phase ${p}` })),
153
- incomplete_phases: incompletePhases,
154
- warnings,
155
- errors,
156
- verdict,
157
- };
158
-
159
- // ── Persist report (latest overwrites — expected behavior) ──
160
- const reportPath = path.join(projectDir, '.sprint-state', 'audit-report.json');
161
- try {
162
- fs.writeFileSync(reportPath, JSON.stringify(result, null, 2), 'utf8');
163
- } catch (err) {
164
- // Report write failure is WARNING, not BLOCK
165
- warnings.push(`Failed to write audit report: ${err.message}`);
166
- }
167
-
168
- // ── Output ──
169
- if (jsonFlag) {
170
- console.log(JSON.stringify(result, null, 2));
171
- } else {
172
- printHumanReadable(result, state);
173
- }
174
-
175
- // Exit code: FAIL → 1, others → 0
176
- return verdict === 'FAIL' ? 1 : 0;
177
- }
178
-
179
- /**
180
- * Print human-readable audit report.
181
- * @param {object} result - Audit result object
182
- * @param {object} state - Sprint state object
183
- */
184
- function printHumanReadable(result, state) {
185
- const lines = [];
186
- lines.push('');
187
- lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
188
- lines.push(` Sprint Audit Report — ${result.sprint_id}`);
189
- lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
190
- lines.push('');
191
- lines.push(` Branch: ${result.branch}`);
192
- lines.push(` Phase Coverage: ${result.coverage.completed}/${result.coverage.total} (${result.coverage.pct}%)`);
193
- lines.push('');
194
- lines.push(' Phase History:');
195
-
196
- for (let i = 1; i <= 6; i++) {
197
- const entry = result.incomplete_phases.find(p => p.phase === i);
198
- const missing = result.missing_phases.find(p => p.phase === i);
199
- const name = (PHASE_NAMES[i] || `Phase ${i}`).padEnd(10);
200
-
201
- if (missing) {
202
- lines.push(` ❌ Phase ${i}/6 ${name} — not recorded`);
203
- } else if (entry) {
204
- lines.push(` ⚠️ Phase ${i}/6 ${name} ${entry.status} ← not yet completed`);
205
- } else {
206
- lines.push(` ✅ Phase ${i}/6 ${name} completed`);
207
- }
208
- }
209
-
210
- if (result.warnings.length > 0) {
211
- lines.push('');
212
- lines.push(' Warnings:');
213
- for (const w of result.warnings) {
214
- lines.push(` ⚠️ ${w}`);
215
- }
216
- }
217
-
218
- if (result.errors.length > 0) {
219
- lines.push('');
220
- lines.push(' Errors:');
221
- for (const e of result.errors) {
222
- lines.push(` ❌ ${e}`);
223
- }
224
- }
225
-
226
- lines.push('');
227
- const issueCount = result.warnings.length + result.errors.length;
228
- lines.push(` Overall: ${result.warnings.length} warning(s), ${result.errors.length} error(s)`);
229
- lines.push(` Verdict: ${result.verdict}`);
230
- lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
231
-
232
- console.log(lines.join('\n'));
233
- }
234
-
235
- module.exports = { handleSprintAudit };
1
+ /**
2
+ * xp-gate sprint-audit — Layer 2: Final sprint completeness audit.
3
+ *
4
+ * Checks phase_history coverage, time records, artifact records,
5
+ * and state consistency at Phase 6 CLOSE.
6
+ *
7
+ * Usage:
8
+ * xp-gate sprint-audit [--dir <path>] [--json]
9
+ *
10
+ * Verdict rules:
11
+ * 6/6 completed + 0 errors → PASS (exit 0)
12
+ * >= 4/6 completed + 0 errors → PASS_WITH_WARNINGS (exit 0)
13
+ * < 4/6 completed or any errors → FAIL (exit 1)
14
+ * No sprint-state.json → SKIP (exit 0)
15
+ *
16
+ * @module sprint-audit
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const { PHASE_NAMES } = require('./phase-transition');
24
+
25
+ /**
26
+ * Run the sprint audit.
27
+ * @param {string[]} args - CLI arguments
28
+ * @returns {Promise<number>} Exit code
29
+ */
30
+ async function handleSprintAudit(args = []) {
31
+ if (args.includes('--help')) {
32
+ console.log('Usage: xp-gate sprint-audit [options]');
33
+ console.log('');
34
+ console.log('Options:');
35
+ console.log(' --dir <path> Project directory (default: cwd)');
36
+ console.log(' --json Output as JSON instead of human-readable');
37
+ console.log('');
38
+ console.log('Runs final sprint completeness audit at Phase 6 CLOSE.');
39
+ console.log('Checks phase coverage, time records, artifact records,');
40
+ console.log('and state consistency.');
41
+ return 0;
42
+ }
43
+
44
+ const dirIdx = args.indexOf('--dir');
45
+ const projectDir = dirIdx >= 0 ? args[dirIdx + 1] : process.cwd();
46
+ const jsonFlag = args.includes('--json');
47
+
48
+ const stateFile = path.join(projectDir, '.sprint-state', 'sprint-state.json');
49
+
50
+ // No sprint-state.json → SKIP
51
+ if (!fs.existsSync(stateFile)) {
52
+ if (jsonFlag) {
53
+ console.log(JSON.stringify({ verdict: 'SKIP', reason: 'No sprint-state.json found' }));
54
+ } else {
55
+ console.log('⏭️ No sprint-state.json found — skipping audit.');
56
+ }
57
+ return 0;
58
+ }
59
+
60
+ let state;
61
+ try {
62
+ state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
63
+ } catch (err) {
64
+ const result = {
65
+ verdict: 'FAIL',
66
+ errors: [`Failed to parse sprint-state.json: ${err.message}`],
67
+ };
68
+ if (jsonFlag) {
69
+ console.log(JSON.stringify(result, null, 2));
70
+ } else {
71
+ console.error(`❌ ${result.errors[0]}`);
72
+ }
73
+ return 1;
74
+ }
75
+
76
+ // ── Run checks ──
77
+ const warnings = [];
78
+ const errors = [];
79
+ const phaseHistory = Array.isArray(state.phase_history) ? state.phase_history : [];
80
+
81
+ // Build lookup
82
+ const historyLookup = {};
83
+ for (const entry of phaseHistory) {
84
+ historyLookup[entry.phase] = entry;
85
+ }
86
+
87
+ // Check 1: Phase coverage
88
+ const completedPhases = [];
89
+ const missingPhases = [];
90
+ const incompletePhases = [];
91
+
92
+ for (let i = 1; i <= 6; i++) {
93
+ const entry = historyLookup[i];
94
+ if (!entry) {
95
+ missingPhases.push(i);
96
+ } else if (entry.status === 'completed' || entry.status === 'skipped') {
97
+ completedPhases.push(i);
98
+ } else {
99
+ incompletePhases.push({ phase: i, name: PHASE_NAMES[i] || `Phase ${i}`, status: entry.status });
100
+ }
101
+ }
102
+
103
+ // Check 2: Time records for completed phases
104
+ for (const phaseNum of completedPhases) {
105
+ const entry = historyLookup[phaseNum];
106
+ if (entry.status === 'completed' && (entry.duration_seconds == null || entry.duration_seconds === 0)) {
107
+ warnings.push(`Phase ${phaseNum} (${PHASE_NAMES[phaseNum]}) completed but has no duration recorded`);
108
+ }
109
+ }
110
+
111
+ // Check 3: Artifact records (phase-{N}-summary.md)
112
+ const outputsDir = path.join(projectDir, '.sprint-state', 'phase-outputs');
113
+ for (const phaseNum of completedPhases) {
114
+ if (historyLookup[phaseNum].status === 'completed') {
115
+ const summaryFile = path.join(outputsDir, `phase-${phaseNum}-summary.md`);
116
+ if (!fs.existsSync(summaryFile)) {
117
+ warnings.push(`Phase ${phaseNum} summary missing: .sprint-state/phase-outputs/phase-${phaseNum}-summary.md`);
118
+ }
119
+ }
120
+ }
121
+
122
+ // Check 4: State consistency — phase field vs phase_history last entry
123
+ if (phaseHistory.length > 0) {
124
+ const lastEntry = phaseHistory[phaseHistory.length - 1];
125
+ if (state.phase !== undefined && state.phase !== lastEntry.phase) {
126
+ errors.push(
127
+ `State inconsistency: sprint-state.phase=${state.phase} but phase_history last entry is phase ${lastEntry.phase}`
128
+ );
129
+ }
130
+ }
131
+
132
+ // ── Determine verdict ──
133
+ const completedCount = completedPhases.length;
134
+ let verdict;
135
+ if (errors.length > 0 || completedCount < 4) {
136
+ verdict = 'FAIL';
137
+ } else if (completedCount >= 6 && errors.length === 0) {
138
+ verdict = 'PASS';
139
+ } else {
140
+ verdict = 'PASS_WITH_WARNINGS';
141
+ }
142
+
143
+ // ── Build result ──
144
+ const result = {
145
+ sprint_id: state.id || 'unknown',
146
+ branch: state.isolation?.branch || 'unknown',
147
+ coverage: {
148
+ completed: completedCount,
149
+ total: 6,
150
+ pct: Math.round((completedCount / 6) * 100),
151
+ },
152
+ missing_phases: missingPhases.map(p => ({ phase: p, name: PHASE_NAMES[p] || `Phase ${p}` })),
153
+ incomplete_phases: incompletePhases,
154
+ warnings,
155
+ errors,
156
+ verdict,
157
+ };
158
+
159
+ // ── Persist report (latest overwrites — expected behavior) ──
160
+ const reportPath = path.join(projectDir, '.sprint-state', 'audit-report.json');
161
+ try {
162
+ fs.writeFileSync(reportPath, JSON.stringify(result, null, 2), 'utf8');
163
+ } catch (err) {
164
+ // Report write failure is WARNING, not BLOCK
165
+ warnings.push(`Failed to write audit report: ${err.message}`);
166
+ }
167
+
168
+ // ── Output ──
169
+ if (jsonFlag) {
170
+ console.log(JSON.stringify(result, null, 2));
171
+ } else {
172
+ printHumanReadable(result, state);
173
+ }
174
+
175
+ // Exit code: FAIL → 1, others → 0
176
+ return verdict === 'FAIL' ? 1 : 0;
177
+ }
178
+
179
+ /**
180
+ * Print human-readable audit report.
181
+ * @param {object} result - Audit result object
182
+ * @param {object} state - Sprint state object
183
+ */
184
+ function printHumanReadable(result, state) {
185
+ const lines = [];
186
+ lines.push('');
187
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
188
+ lines.push(` Sprint Audit Report — ${result.sprint_id}`);
189
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
190
+ lines.push('');
191
+ lines.push(` Branch: ${result.branch}`);
192
+ lines.push(` Phase Coverage: ${result.coverage.completed}/${result.coverage.total} (${result.coverage.pct}%)`);
193
+ lines.push('');
194
+ lines.push(' Phase History:');
195
+
196
+ for (let i = 1; i <= 6; i++) {
197
+ const entry = result.incomplete_phases.find(p => p.phase === i);
198
+ const missing = result.missing_phases.find(p => p.phase === i);
199
+ const name = (PHASE_NAMES[i] || `Phase ${i}`).padEnd(10);
200
+
201
+ if (missing) {
202
+ lines.push(` ❌ Phase ${i}/6 ${name} — not recorded`);
203
+ } else if (entry) {
204
+ lines.push(` ⚠️ Phase ${i}/6 ${name} ${entry.status} ← not yet completed`);
205
+ } else {
206
+ lines.push(` ✅ Phase ${i}/6 ${name} completed`);
207
+ }
208
+ }
209
+
210
+ if (result.warnings.length > 0) {
211
+ lines.push('');
212
+ lines.push(' Warnings:');
213
+ for (const w of result.warnings) {
214
+ lines.push(` ⚠️ ${w}`);
215
+ }
216
+ }
217
+
218
+ if (result.errors.length > 0) {
219
+ lines.push('');
220
+ lines.push(' Errors:');
221
+ for (const e of result.errors) {
222
+ lines.push(` ❌ ${e}`);
223
+ }
224
+ }
225
+
226
+ lines.push('');
227
+ const issueCount = result.warnings.length + result.errors.length;
228
+ lines.push(` Overall: ${result.warnings.length} warning(s), ${result.errors.length} error(s)`);
229
+ lines.push(` Verdict: ${result.verdict}`);
230
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
231
+
232
+ console.log(lines.join('\n'));
233
+ }
234
+
235
+ module.exports = { handleSprintAudit };