@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.
Files changed (37) hide show
  1. package/bin/xp-gate.js +23 -0
  2. package/hooks/pre-commit +92 -0
  3. package/lib/__tests__/doctor-drift-direction.test.js +104 -0
  4. package/lib/__tests__/gate-coverage.test.js +137 -0
  5. package/lib/__tests__/rework.test.js +127 -0
  6. package/lib/__tests__/test-alignment.test.ts +45 -4
  7. package/lib/doctor.js +82 -8
  8. package/lib/gate-audit.ts +8 -0
  9. package/lib/gate-coverage.js +315 -0
  10. package/lib/mattpocock-license-verdict.json +1 -1
  11. package/lib/rework.js +262 -0
  12. package/lib/test-alignment.ts +27 -7
  13. package/mock-policy/AGENTS.md +2 -2
  14. package/mutation/AGENTS.md +2 -2
  15. package/package.json +1 -1
  16. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  17. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  18. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  19. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  20. package/plugins/dsh/package.json +1 -1
  21. package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
  22. package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
  23. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
  24. package/plugins/opencode/package.json +1 -1
  25. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  26. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  27. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  28. package/plugins/qoder/plugin.json +1 -1
  29. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  30. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  31. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  32. package/principles/AGENTS.md +2 -2
  33. package/scripts/__tests__/sync-package-content-isolation.test.js +73 -0
  34. package/scripts/__tests__/sync-package-content.test.js +109 -2
  35. package/skills/delphi-review/AGENTS.md +2 -2
  36. package/skills/sprint-flow/AGENTS.md +2 -2
  37. 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
+ }
@@ -670,13 +670,32 @@ export function calculateScore(
670
670
  // Step 5: writeReport()
671
671
  // ============================================================================
672
672
 
673
- export function writeReport(report: TestAlignmentReportFile, outputPath: string): void {
674
- const dir = path.dirname(outputPath);
675
- if (!fs.existsSync(dir)) {
676
- fs.mkdirSync(dir, { recursive: true });
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
  }
@@ -1,9 +1,9 @@
1
1
  # SRC/MOCK-POLICY KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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.
@@ -1,9 +1,9 @@
1
1
  # SRC/MUTATION KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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": "@boyingliu01/xp-gate",
3
- "version": "0.35.0",
3
+ "version": "0.35.2",
4
4
  "description": "AI-driven development workflow: 6 quality gates + Delphi review + Sprint Flow",
5
5
  "bin": {
6
6
  "xp-gate": "bin/xp-gate.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.35.0",
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:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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": "@boyingliu01/dsh-plugin-xp-gate",
3
- "version": "0.35.0",
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:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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": "@boyingliu01/opencode-plugin",
3
- "version": "0.35.0",
3
+ "version": "0.35.2",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "description": "XP-Gate quality gates + AI workflow skills + Sprint Flow TUI sidebar for OpenCode",
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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.0",
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:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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
  # PRINCIPLES CHECKER MODULE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** c8a73b2a
4
+ **Commit:** 0a400e4c
5
5
  **Branch:** main
6
- **Version:** 0.35.0.0
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
+ });