@boyingliu01/xp-gate 0.18.1 → 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.
- package/adapters/gate-3.sh +68 -0
- package/adapters/gate-4.sh +80 -0
- package/adapters/gate-7.sh +45 -0
- package/adapters/gate-8.sh +56 -0
- package/adapters/gate-9.sh +105 -0
- package/bin/xp-gate.js +45 -0
- package/hooks/pre-commit +108 -9
- package/hooks/pre-push +63 -232
- package/lib/__tests__/install-skill.test.js +3 -3
- package/lib/__tests__/test-alignment.test.ts +761 -0
- package/lib/install-skill.js +11 -3
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/phase-transition.js +106 -1
- package/lib/sprint-init.js +41 -0
- 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/skills/delphi-review/AGENTS.md +3 -3
- 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/sprint-flow/AGENTS.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- 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/skills/delphi-review/AGENTS.md +3 -3
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/test-specification-alignment/AGENTS.md +3 -3
package/lib/install-skill.js
CHANGED
|
@@ -30,10 +30,18 @@ function getCliVersion() {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const SKILLS_REGISTRY = {
|
|
33
|
-
'
|
|
33
|
+
'admin-template-guidelines': { repo: 'boyingliu01/xp-gate', path: 'skills/admin-template-guidelines' },
|
|
34
|
+
'batch-grill-me': { repo: 'boyingliu01/xp-gate', path: 'skills/batch-grill-me' },
|
|
34
35
|
'delphi-review': { repo: 'boyingliu01/xp-gate', path: 'skills/delphi-review' },
|
|
35
|
-
'
|
|
36
|
-
'
|
|
36
|
+
'domain-modeling': { repo: 'boyingliu01/xp-gate', path: 'skills/domain-modeling' },
|
|
37
|
+
'grill-with-docs': { repo: 'boyingliu01/xp-gate', path: 'skills/grill-with-docs' },
|
|
38
|
+
'grilling': { repo: 'boyingliu01/xp-gate', path: 'skills/grilling' },
|
|
39
|
+
'improve-codebase-architecture': { repo: 'boyingliu01/xp-gate', path: 'skills/improve-codebase-architecture' },
|
|
40
|
+
'ralph-loop': { repo: 'boyingliu01/xp-gate', path: 'skills/ralph-loop' },
|
|
41
|
+
'sprint-flow': { repo: 'boyingliu01/xp-gate', path: 'skills/sprint-flow' },
|
|
42
|
+
'test-driven-development': { repo: 'boyingliu01/xp-gate', path: 'skills/test-driven-development' },
|
|
43
|
+
'test-specification-alignment': { repo: 'boyingliu01/xp-gate', path: 'skills/test-specification-alignment' },
|
|
44
|
+
'to-issues': { repo: 'boyingliu01/xp-gate', path: 'skills/to-issues' }
|
|
37
45
|
};
|
|
38
46
|
|
|
39
47
|
async function installSkill(name, options = {}) {
|
package/lib/phase-transition.js
CHANGED
|
@@ -496,4 +496,109 @@ async function handlePhaseTransition(args = []) {
|
|
|
496
496
|
}
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
-
|
|
499
|
+
/**
|
|
500
|
+
* Read-only evidence check for a given phase. Validates evidence without modifying sprint state.
|
|
501
|
+
* D3 (Unify Evidence Validation): replaces sprint-gate.sh inline JSON parsing.
|
|
502
|
+
*
|
|
503
|
+
* @param {number} phase - Phase number (1-6)
|
|
504
|
+
* @param {string} projectDir - Project root directory
|
|
505
|
+
* @returns {{ ok: boolean, errors: string[], warnings: string[] }}
|
|
506
|
+
*/
|
|
507
|
+
function checkEvidence(phase, projectDir) {
|
|
508
|
+
return validateEvidence(phase, projectDir);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Validate .code-walkthrough-result.json for code walkthrough mode.
|
|
513
|
+
* Ported from githooks/pre-push Gate MW inline jq logic.
|
|
514
|
+
*
|
|
515
|
+
* @param {string} projectDir - Project root directory
|
|
516
|
+
* @returns {{ ok: boolean, errors: string[], warnings: string[] }}
|
|
517
|
+
*/
|
|
518
|
+
function checkWalkthrough(projectDir) {
|
|
519
|
+
const walkthroughFile = path.join(projectDir, '.code-walkthrough-result.json');
|
|
520
|
+
const errors = [];
|
|
521
|
+
const warnings = [];
|
|
522
|
+
|
|
523
|
+
if (!fs.existsSync(walkthroughFile)) {
|
|
524
|
+
errors.push('.code-walkthrough-result.json not found — run delphi-review --mode code-walkthrough before push');
|
|
525
|
+
return { ok: false, errors, warnings };
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
let data;
|
|
529
|
+
try {
|
|
530
|
+
data = JSON.parse(fs.readFileSync(walkthroughFile, 'utf8'));
|
|
531
|
+
} catch {
|
|
532
|
+
errors.push('.code-walkthrough-result.json is malformed JSON');
|
|
533
|
+
return { ok: false, errors, warnings };
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Check verdict
|
|
537
|
+
if (data.verdict !== 'APPROVED') {
|
|
538
|
+
errors.push(`Code walkthrough verdict is "${data.verdict}" — must be APPROVED`);
|
|
539
|
+
return { ok: false, errors, warnings };
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Check commit is ancestor of HEAD
|
|
543
|
+
if (data.commit) {
|
|
544
|
+
try {
|
|
545
|
+
execSync(`git merge-base --is-ancestor ${data.commit} HEAD`, {
|
|
546
|
+
cwd: projectDir, stdio: 'pipe',
|
|
547
|
+
});
|
|
548
|
+
} catch {
|
|
549
|
+
errors.push(`Code walkthrough commit ${data.commit} is not an ancestor of HEAD`);
|
|
550
|
+
return { ok: false, errors, warnings };
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Check expiration
|
|
555
|
+
if (data.expires) {
|
|
556
|
+
const expiry = new Date(data.expires).getTime();
|
|
557
|
+
if (Date.now() > expiry) {
|
|
558
|
+
errors.push(`Code walkthrough expired at ${data.expires}`);
|
|
559
|
+
return { ok: false, errors, warnings };
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return { ok: true, errors, warnings };
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* D8 Layer 3: Check bypass audit log for any --no-verify bypassed commits on the current branch.
|
|
568
|
+
* Blocks phase transition if bypassed commits exist.
|
|
569
|
+
*
|
|
570
|
+
* @param {string} projectDir - Project root directory
|
|
571
|
+
* @returns {{ ok: boolean, bypassedCommits: string[] }}
|
|
572
|
+
*/
|
|
573
|
+
function checkBypassAudit(projectDir) {
|
|
574
|
+
const auditFile = path.join(projectDir, '.xp-gate', 'bypass-audit.jsonl');
|
|
575
|
+
const bypassedCommits = [];
|
|
576
|
+
|
|
577
|
+
if (!fs.existsSync(auditFile)) {
|
|
578
|
+
return { ok: true, bypassedCommits };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const lines = fs.readFileSync(auditFile, 'utf8').trim().split('\n').filter(Boolean);
|
|
582
|
+
for (const line of lines) {
|
|
583
|
+
try {
|
|
584
|
+
const entry = JSON.parse(line);
|
|
585
|
+
if (entry.type === 'precommit_bypass' && entry.commit) {
|
|
586
|
+
// Verify the commit is on the current branch
|
|
587
|
+
try {
|
|
588
|
+
execSync(`git merge-base --is-ancestor ${entry.commit} HEAD`, {
|
|
589
|
+
cwd: projectDir, stdio: 'pipe',
|
|
590
|
+
});
|
|
591
|
+
bypassedCommits.push(entry.commit);
|
|
592
|
+
} catch {
|
|
593
|
+
// Commit not on current branch — not relevant
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
} catch {
|
|
597
|
+
// Skip malformed lines
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return { ok: bypassedCommits.length === 0, bypassedCommits };
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
module.exports = { handlePhaseTransition, renderDashboard, PHASE_NAMES, validateEvidence, checkEvidence, checkWalkthrough, checkBypassAudit };
|
package/lib/sprint-init.js
CHANGED
|
@@ -166,10 +166,32 @@ async function handleSprintInit(args = []) {
|
|
|
166
166
|
return 0;
|
|
167
167
|
}
|
|
168
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
|
+
|
|
169
190
|
// Create new sprint state
|
|
170
191
|
const sprintId = generateSprintId();
|
|
171
192
|
const newState = {
|
|
172
193
|
_schema_version: 1,
|
|
194
|
+
evidence_schema_version: 2, // Phase 5: new sprints default to full BLOCK enforcement
|
|
173
195
|
id: sprintId,
|
|
174
196
|
task_description: taskDescription.trim(),
|
|
175
197
|
phase: 1,
|
|
@@ -191,6 +213,25 @@ async function handleSprintInit(args = []) {
|
|
|
191
213
|
metrics: {},
|
|
192
214
|
};
|
|
193
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
|
+
|
|
194
235
|
// Write state atomically
|
|
195
236
|
manager.write(newState);
|
|
196
237
|
|