@boyingliu01/xp-gate 0.35.0 → 0.35.2
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/bin/xp-gate.js +23 -0
- package/hooks/pre-commit +92 -0
- package/lib/__tests__/doctor-drift-direction.test.js +104 -0
- package/lib/__tests__/gate-coverage.test.js +137 -0
- package/lib/__tests__/rework.test.js +127 -0
- package/lib/__tests__/test-alignment.test.ts +45 -4
- package/lib/doctor.js +82 -8
- package/lib/gate-audit.ts +8 -0
- package/lib/gate-coverage.js +315 -0
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/rework.js +262 -0
- package/lib/test-alignment.ts +27 -7
- package/mock-policy/AGENTS.md +2 -2
- package/mutation/AGENTS.md +2 -2
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
- package/principles/AGENTS.md +2 -2
- package/scripts/__tests__/sync-package-content-isolation.test.js +73 -0
- package/scripts/__tests__/sync-package-content.test.js +109 -2
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/sprint-flow/AGENTS.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +2 -2
package/lib/rework.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `xp-gate rework` — what the gate journal says about rework (#651).
|
|
3
|
+
*
|
|
4
|
+
* `xp-gate retro` already answers "how much did we ship". This answers the
|
|
5
|
+
* narrower and more actionable question: how often does a round of work clear
|
|
6
|
+
* the gates on the first attempt, and which rule stops it most.
|
|
7
|
+
*
|
|
8
|
+
* The reason this needs a dedicated command rather than a `retro` line: before
|
|
9
|
+
* #651 the journal contained NO failure records at all, so a naive rework rate
|
|
10
|
+
* computed from it returned 100% every time. A metric that reads "perfect" when
|
|
11
|
+
* it is actually unmeasured is worse than no metric -- it manufactures
|
|
12
|
+
* confidence. So `measurable: false` is a first-class outcome here.
|
|
13
|
+
*
|
|
14
|
+
* No runtime dependencies: this ships inside the zero-install package.
|
|
15
|
+
*/
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const fs = require('node:fs');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
|
|
21
|
+
const AUDIT_DIR = '.xp-gate';
|
|
22
|
+
const AUDIT_FILE = 'audit.jsonl';
|
|
23
|
+
const PENDING_FILE = 'audit.pending.jsonl';
|
|
24
|
+
|
|
25
|
+
/** Bucket name for failures the journal cannot attribute to a rule. */
|
|
26
|
+
const UNATTRIBUTED = 'unattributed';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read every journal record under `repoRoot`, oldest first.
|
|
30
|
+
*
|
|
31
|
+
* Malformed lines are skipped rather than thrown on: the journal is append-only
|
|
32
|
+
* and a single truncated write must not take the whole report down.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} [repoRoot]
|
|
35
|
+
* @returns {Array<object>}
|
|
36
|
+
*/
|
|
37
|
+
function readJournal(repoRoot = process.cwd()) {
|
|
38
|
+
const dir = path.join(repoRoot, AUDIT_DIR);
|
|
39
|
+
const entries = [];
|
|
40
|
+
for (const file of [AUDIT_FILE, PENDING_FILE]) {
|
|
41
|
+
const full = path.join(dir, file);
|
|
42
|
+
let raw;
|
|
43
|
+
try {
|
|
44
|
+
raw = fs.readFileSync(full, 'utf8');
|
|
45
|
+
} catch {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
for (const line of raw.split('\n')) {
|
|
49
|
+
if (!line.trim()) continue;
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(line);
|
|
52
|
+
if (parsed && typeof parsed === 'object') entries.push(parsed);
|
|
53
|
+
} catch {
|
|
54
|
+
/* truncated/garbled line -- skip, do not fail the report */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return entries;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Median of a numeric array (0 for an empty array). */
|
|
62
|
+
function median(values) {
|
|
63
|
+
if (values.length === 0) return 0;
|
|
64
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
65
|
+
const mid = Math.floor(sorted.length / 2);
|
|
66
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Mean of a numeric array (0 for an empty array). */
|
|
70
|
+
function mean(values) {
|
|
71
|
+
if (values.length === 0) return 0;
|
|
72
|
+
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Group a flat record list into rework rounds.
|
|
77
|
+
*
|
|
78
|
+
* The journal is a flat stream of gate executions with no round identifier, so a
|
|
79
|
+
* round is reconstructed the only way the data allows: a run of FAIL records
|
|
80
|
+
* terminated by the next PASS. That is exactly "how many attempts before this
|
|
81
|
+
* landed", which is the definition rework needs. A trailing run of failures with
|
|
82
|
+
* no resolving pass is a round that has not landed yet, and is counted as
|
|
83
|
+
* `unresolved` rather than being dropped.
|
|
84
|
+
*
|
|
85
|
+
* @param {Array<object>} entries
|
|
86
|
+
*/
|
|
87
|
+
function groupRounds(entries) {
|
|
88
|
+
const rounds = [];
|
|
89
|
+
let current = 0;
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
if (entry.passed) {
|
|
92
|
+
if (current > 0) rounds.push(current);
|
|
93
|
+
current = 0;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
current += 1;
|
|
97
|
+
}
|
|
98
|
+
return { resolved: rounds, unresolved: current };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Aggregate the journal into rework metrics.
|
|
103
|
+
*
|
|
104
|
+
* @param {Array<object>} entries journal records
|
|
105
|
+
* @returns {object} see AC-670-59..62
|
|
106
|
+
*/
|
|
107
|
+
function computeReworkStats(entries) {
|
|
108
|
+
const list = Array.isArray(entries) ? entries : [];
|
|
109
|
+
const totals = list.length;
|
|
110
|
+
const failures = list.filter((e) => e.passed === false).length;
|
|
111
|
+
const passes = totals - failures;
|
|
112
|
+
|
|
113
|
+
// ── Failures by reason ────────────────────────────────────────────────────
|
|
114
|
+
// Unlabelled failures are bucketed, never dropped: dropping them would lower
|
|
115
|
+
// the failure count and push the first-pass rate UP, which is the optimistic
|
|
116
|
+
// direction and therefore the dangerous one.
|
|
117
|
+
const byReason = new Map();
|
|
118
|
+
for (const entry of list) {
|
|
119
|
+
if (entry.passed !== false) continue;
|
|
120
|
+
const raw = typeof entry.failure_reason === 'string' ? entry.failure_reason.trim() : '';
|
|
121
|
+
const key = raw === '' ? UNATTRIBUTED : raw;
|
|
122
|
+
byReason.set(key, (byReason.get(key) || 0) + 1);
|
|
123
|
+
}
|
|
124
|
+
const topFailures = [...byReason.entries()]
|
|
125
|
+
.map(([reason, count]) => ({ reason, count }))
|
|
126
|
+
.sort((a, b) => b.count - a.count || a.reason.localeCompare(b.reason));
|
|
127
|
+
|
|
128
|
+
// ── Durations ─────────────────────────────────────────────────────────────
|
|
129
|
+
// Two independent guards, because the flag alone is not enough:
|
|
130
|
+
//
|
|
131
|
+
// 1. honour `duration_anomaly` -- the writer's own verdict;
|
|
132
|
+
// 2. reject implausible magnitudes. Measured on the real journal: 391 records
|
|
133
|
+
// carry the flag, but 6 MORE epoch-sized values (up to 1.78e12) do not,
|
|
134
|
+
// so trusting the flag alone left the mean at 1.3e9 ms -- the very
|
|
135
|
+
// "average 430 million seconds" artifact this is supposed to kill.
|
|
136
|
+
//
|
|
137
|
+
// The ceiling is deliberately generous (1 hour): a real gate run that long is
|
|
138
|
+
// pathological but conceivable, while an epoch stamp is ~1.78e12 and cannot be
|
|
139
|
+
// mistaken for anything else.
|
|
140
|
+
const MAX_PLAUSIBLE_DURATION_MS = 60 * 60 * 1000;
|
|
141
|
+
const isPlausible = (e) => typeof e.duration_ms === 'number'
|
|
142
|
+
&& Number.isFinite(e.duration_ms)
|
|
143
|
+
&& e.duration_ms >= 0
|
|
144
|
+
&& e.duration_ms <= MAX_PLAUSIBLE_DURATION_MS
|
|
145
|
+
&& e.duration_anomaly !== true;
|
|
146
|
+
const sane = list.filter(isPlausible);
|
|
147
|
+
const duration = sane.length > 0
|
|
148
|
+
? {
|
|
149
|
+
medianMs: median(sane.map((e) => e.duration_ms)),
|
|
150
|
+
meanMs: Math.round(mean(sane.map((e) => e.duration_ms))),
|
|
151
|
+
samples: sane.length,
|
|
152
|
+
// Every record that did NOT contribute -- flagged or implausible. Reporting
|
|
153
|
+
// only the flagged count would understate the exclusion and make the
|
|
154
|
+
// sample size look closer to the record count than it is.
|
|
155
|
+
excludedAnomalies: list.filter((e) => typeof e.duration_ms === 'number' && !isPlausible(e)).length,
|
|
156
|
+
}
|
|
157
|
+
: null;
|
|
158
|
+
|
|
159
|
+
// ── Rounds ────────────────────────────────────────────────────────────────
|
|
160
|
+
const rounds = groupRounds(list);
|
|
161
|
+
const reworkRounds = rounds.resolved;
|
|
162
|
+
|
|
163
|
+
// ── First-pass rate ───────────────────────────────────────────────────────
|
|
164
|
+
// Undefined without failures. A rate over an empty failure set is not 100%.
|
|
165
|
+
const measurable = failures > 0;
|
|
166
|
+
const firstPassRate = measurable ? Math.round((passes / totals) * 1000) / 10 : null;
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
measurable,
|
|
170
|
+
reason: measurable ? null : 'no failure records in the journal -- first-pass rate is not measurable (pre-#651 journals only recorded passes)',
|
|
171
|
+
totalRecords: totals,
|
|
172
|
+
passes,
|
|
173
|
+
failures,
|
|
174
|
+
firstPassRate,
|
|
175
|
+
rounds: {
|
|
176
|
+
resolved: reworkRounds.length,
|
|
177
|
+
unresolved: rounds.unresolved > 0,
|
|
178
|
+
medianRework: median(reworkRounds),
|
|
179
|
+
meanRework: Math.round(mean(reworkRounds) * 100) / 100,
|
|
180
|
+
worstRework: reworkRounds.length > 0 ? Math.max(...reworkRounds) : 0,
|
|
181
|
+
},
|
|
182
|
+
topFailures,
|
|
183
|
+
duration,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Render a stats object for the terminal. */
|
|
188
|
+
function formatReworkReport(stats, { limit = 10 } = {}) {
|
|
189
|
+
const lines = [];
|
|
190
|
+
lines.push('');
|
|
191
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
192
|
+
lines.push(' REWORK REPORT');
|
|
193
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
194
|
+
lines.push('');
|
|
195
|
+
lines.push(` Journal records: ${stats.totalRecords} (${stats.passes} pass, ${stats.failures} fail)`);
|
|
196
|
+
|
|
197
|
+
if (!stats.measurable) {
|
|
198
|
+
// Say WHY, and say it loudly: a silent 100% would be a lie.
|
|
199
|
+
lines.push('');
|
|
200
|
+
lines.push(' ⚠️ First-pass rate: NOT MEASURABLE');
|
|
201
|
+
lines.push(` ${stats.reason}`);
|
|
202
|
+
} else {
|
|
203
|
+
lines.push(` First-pass rate: ${stats.firstPassRate}%`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (stats.rounds.resolved > 0) {
|
|
207
|
+
lines.push('');
|
|
208
|
+
lines.push(` Rework rounds (failures before a landing): ${stats.rounds.resolved}`);
|
|
209
|
+
lines.push(` median ${stats.rounds.medianRework} / mean ${stats.rounds.meanRework} / worst ${stats.rounds.worstRework}`);
|
|
210
|
+
if (stats.rounds.unresolved) {
|
|
211
|
+
lines.push(' (a trailing run of failures has not landed yet)');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (stats.topFailures.length > 0) {
|
|
216
|
+
lines.push('');
|
|
217
|
+
lines.push(` Top ${limit} blocking reasons:`);
|
|
218
|
+
for (const { reason, count } of stats.topFailures.slice(0, limit)) {
|
|
219
|
+
lines.push(` ${String(count).padStart(5)} ${reason}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (stats.duration) {
|
|
224
|
+
lines.push('');
|
|
225
|
+
lines.push(` Gate duration: median ${stats.duration.medianMs} ms / mean ${stats.duration.meanMs} ms (${stats.duration.samples} samples`);
|
|
226
|
+
lines.push(` ${stats.duration.excludedAnomalies} anomaly record(s) excluded)`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
lines.push('');
|
|
230
|
+
return lines.join('\n');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* CLI entry.
|
|
235
|
+
* @param {string[]} argv args after `rework`
|
|
236
|
+
*/
|
|
237
|
+
function main(argv = []) {
|
|
238
|
+
const repoRootIdx = argv.indexOf('--dir');
|
|
239
|
+
const repoRoot = repoRootIdx !== -1 && argv[repoRootIdx + 1] ? argv[repoRootIdx + 1] : process.cwd();
|
|
240
|
+
const limitIdx = argv.indexOf('--top');
|
|
241
|
+
const limit = limitIdx !== -1 && argv[limitIdx + 1] ? parseInt(argv[limitIdx + 1], 10) : 10;
|
|
242
|
+
|
|
243
|
+
const stats = computeReworkStats(readJournal(repoRoot));
|
|
244
|
+
if (argv.includes('--json')) {
|
|
245
|
+
process.stdout.write(`${JSON.stringify(stats, null, 2)}\n`);
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
process.stdout.write(`${formatReworkReport(stats, { limit })}\n`);
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = {
|
|
253
|
+
readJournal,
|
|
254
|
+
computeReworkStats,
|
|
255
|
+
formatReworkReport,
|
|
256
|
+
main,
|
|
257
|
+
UNATTRIBUTED,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
if (require.main === module) {
|
|
261
|
+
process.exitCode = main(process.argv.slice(2));
|
|
262
|
+
}
|
package/lib/test-alignment.ts
CHANGED
|
@@ -670,13 +670,32 @@ export function calculateScore(
|
|
|
670
670
|
// Step 5: writeReport()
|
|
671
671
|
// ============================================================================
|
|
672
672
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
673
|
+
/**
|
|
674
|
+
* Write the alignment report, recording (not throwing) a write failure.
|
|
675
|
+
*
|
|
676
|
+
* This is the LAST step of runFullAlignment — after the spec is parsed and every
|
|
677
|
+
* test file is scanned. Letting a write error escape discards all of that work
|
|
678
|
+
* and turns a reportable condition (disk full, read-only mount, permission
|
|
679
|
+
* denied) into a crashed command with no report at all. The report schema
|
|
680
|
+
* already carries an `errors` array for exactly this kind of condition.
|
|
681
|
+
*
|
|
682
|
+
* @returns true when the file was written, false when the failure was recorded.
|
|
683
|
+
*/
|
|
684
|
+
export function writeReport(report: TestAlignmentReportFile, outputPath: string): boolean {
|
|
685
|
+
try {
|
|
686
|
+
const dir = path.dirname(outputPath);
|
|
687
|
+
if (!fs.existsSync(dir)) {
|
|
688
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
689
|
+
}
|
|
690
|
+
fs.writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf8');
|
|
691
|
+
return true;
|
|
692
|
+
} catch (error) {
|
|
693
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
694
|
+
report.errors.push(`Failed to write report to ${outputPath}: ${reason}`);
|
|
695
|
+
process.stderr.write(`⚠️ Failed to write alignment report to ${outputPath}: ${reason}
|
|
696
|
+
`);
|
|
697
|
+
return false;
|
|
677
698
|
}
|
|
678
|
-
|
|
679
|
-
fs.writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf8');
|
|
680
699
|
}
|
|
681
700
|
|
|
682
701
|
// ============================================================================
|
|
@@ -756,7 +775,8 @@ export function runFullAlignment(options: AlignmentOptions): TestAlignmentReport
|
|
|
756
775
|
errors: [],
|
|
757
776
|
};
|
|
758
777
|
|
|
759
|
-
// Step 5: Write output
|
|
778
|
+
// Step 5: Write output. A failure is recorded on `report.errors` by writeReport
|
|
779
|
+
// rather than thrown — the caller still gets the analysis it just paid for.
|
|
760
780
|
if (outputPath) {
|
|
761
781
|
writeReport(report, outputPath);
|
|
762
782
|
}
|
package/mock-policy/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MOCK-POLICY KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Mock layering policy enforcement — Gate M3 of pre-push hook. Ensures integration tests use real implementations for internal dependencies, mock external dependencies, and annotate pending mocks with removal plans. Combines project scope scanning, mock decision engine, and per-file validation into a single pipeline.
|
package/mutation/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MUTATION KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**Gate M** (incremental mutation testing) + **Gate M2** helpers (test-layer detection used by `src/mock-policy/`). Pre-push quality gate. TypeScript-only; uses Stryker.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xp-gate",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.2",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
|
|
6
6
|
"author": {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): 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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
package/plugins/dsh/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boyingliu01/dsh-plugin-xp-gate",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.2",
|
|
4
4
|
"description": "XP-Gate deterministic quality gates + AI workflow skills as a DeepSeek Harness (DSH) native plugin: gate-check / gate-principles / gate-arch tools + 12 SKILL.md skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): 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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): 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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xp-gate",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.2",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Qoder. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
|
|
6
6
|
"author": {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): 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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
package/principles/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# PRINCIPLES CHECKER MODULE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 0a400e4c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.2.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Clean Code & SOLID principles checker — **Gate 4** of pre-commit. 14 rules × 9 language adapters, SARIF 2.1.0 output. Houses the **Boy Scout Rule** enforcement engine (Gate 6) and warning-baseline storage.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-645
|
|
3
|
+
* @intent `sync-package-content.test.js` used the REAL repository root as the
|
|
4
|
+
* spawn cwd:
|
|
5
|
+
*
|
|
6
|
+
* const WORKTREE_PATH = path.resolve(__dirname, '..', '..', '..', '..');
|
|
7
|
+
*
|
|
8
|
+
* Four levels up from `src/npm-package/scripts/__tests__/` is the repo
|
|
9
|
+
* root, not a temp dir. The tests then ran the real sync script with
|
|
10
|
+
* `cwd: WORKTREE_PATH`, so the script's writes landed in the repository
|
|
11
|
+
* instead of a sandbox. CI's Gate 5 failed at `vitest-worktree-guard`:
|
|
12
|
+
*
|
|
13
|
+
* Work tree polluted by the test run — 1 path(s) the run introduced:
|
|
14
|
+
* M src/npm-package/mock-policy/gate-m3.ts
|
|
15
|
+
*
|
|
16
|
+
* The reason this is easy to miss: the pollution is CONDITIONAL. The
|
|
17
|
+
* script's writes are idempotent, so when the mirror already matches the
|
|
18
|
+
* source nothing changes and the run looks clean. It only dirties the tree
|
|
19
|
+
* when the checked-out commit happens to carry a stale mirror — which is
|
|
20
|
+
* exactly when CI is checking out a commit that the fix was supposed to
|
|
21
|
+
* validate. So it presents as "flaky, just re-run it", and re-running
|
|
22
|
+
* hides the cause while leaving the latent defect in place.
|
|
23
|
+
*
|
|
24
|
+
* The guard catching it is good, but it catches the CONSEQUENCE. This file
|
|
25
|
+
* pins the CAUSE: a test may not write into the repository.
|
|
26
|
+
* @covers AC-670-54, AC-670-55
|
|
27
|
+
*/
|
|
28
|
+
'use strict';
|
|
29
|
+
|
|
30
|
+
const fs = require('node:fs');
|
|
31
|
+
const path = require('node:path');
|
|
32
|
+
|
|
33
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
|
|
34
|
+
const TEST_FILE = path.join(
|
|
35
|
+
REPO_ROOT, 'src', 'npm-package', 'scripts', '__tests__', 'sync-package-content.test.js',
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
function readSuite() {
|
|
39
|
+
return fs.readFileSync(TEST_FILE, 'utf8');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('sync-package-content tests must not write into the repository (#645)', () => {
|
|
43
|
+
it('AC-670-54: no test uses the repo root as a spawn cwd', () => {
|
|
44
|
+
const source = readSuite();
|
|
45
|
+
// The exact offending expression. `path.resolve(__dirname, '..' x4)` from the
|
|
46
|
+
// test file IS the repo root -- there is no temp dir anywhere in that chain.
|
|
47
|
+
const offenders = source
|
|
48
|
+
.split('\n')
|
|
49
|
+
.map((line, i) => ({ line: line.trim(), n: i + 1 }))
|
|
50
|
+
.filter((x) => /WORKTREE_PATH\s*=\s*path\.resolve\(__dirname/.test(x.line));
|
|
51
|
+
expect(
|
|
52
|
+
offenders,
|
|
53
|
+
`tests still anchor on the real repo root:\n${offenders.map((o) => ` :${o.n} ${o.line}`).join('\n')}`,
|
|
54
|
+
).toEqual([]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('AC-670-55: the sync tests run against a temp worktree, not the checkout', () => {
|
|
58
|
+
const source = readSuite();
|
|
59
|
+
// Whatever the variable is called, the cwd handed to a spawned sync must be
|
|
60
|
+
// one the test CREATED. mkdtemp is the only way to guarantee that: a path
|
|
61
|
+
// built from __dirname is by construction inside the repository.
|
|
62
|
+
expect(source, 'suite must create a throwaway worktree').toMatch(/mkdtemp/);
|
|
63
|
+
// Every spawnSync `cwd:` must point at that temp root, never at the checkout.
|
|
64
|
+
const cwdLines = source
|
|
65
|
+
.split('\n')
|
|
66
|
+
.filter((line) => /^\s*cwd:\s*/.test(line))
|
|
67
|
+
.map((line) => line.trim());
|
|
68
|
+
expect(cwdLines.length, 'expected at least one spawn cwd').toBeGreaterThan(0);
|
|
69
|
+
for (const line of cwdLines) {
|
|
70
|
+
expect(line, `spawn cwd escapes the sandbox: ${line}`).not.toMatch(/cwd:\s*REPO_ROOT/);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|