@boyingliu01/xp-gate 0.34.1 → 0.35.1

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 (38) hide show
  1. package/bin/xp-gate.js +8 -0
  2. package/gate-3.sh +46 -2
  3. package/gate-4.sh +20 -3
  4. package/hooks/gate-3.sh +46 -2
  5. package/hooks/gate-4.sh +20 -3
  6. package/hooks/lib/ccn-ratchet.sh +109 -0
  7. package/hooks/pre-commit +92 -0
  8. package/lib/__tests__/doctor-timeout-partial.test.js +79 -0
  9. package/lib/__tests__/rework.test.js +127 -0
  10. package/lib/doctor.js +60 -3
  11. package/lib/gate-audit.ts +8 -0
  12. package/lib/mattpocock-license-verdict.json +1 -1
  13. package/lib/rework.js +262 -0
  14. package/mock-policy/AGENTS.md +2 -2
  15. package/mutation/AGENTS.md +2 -2
  16. package/package.json +1 -1
  17. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  18. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  19. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  20. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  21. package/plugins/dsh/package.json +1 -1
  22. package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
  23. package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
  24. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
  25. package/plugins/opencode/package.json +1 -1
  26. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  27. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  28. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  29. package/plugins/qoder/plugin.json +1 -1
  30. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  31. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  32. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  33. package/principles/AGENTS.md +2 -2
  34. package/scripts/__tests__/sync-package-content-isolation.test.js +73 -0
  35. package/scripts/__tests__/sync-package-content.test.js +109 -2
  36. package/skills/delphi-review/AGENTS.md +2 -2
  37. package/skills/sprint-flow/AGENTS.md +2 -2
  38. package/skills/test-specification-alignment/AGENTS.md +2 -2
@@ -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
+ });
@@ -27,6 +27,37 @@ function writeDoc(filePath, content) {
27
27
  fs.writeFileSync(filePath, content, 'utf8');
28
28
  }
29
29
 
30
+ /**
31
+ * Copy the canonical source trees a sync run READS into `destRoot` (#645).
32
+ *
33
+ * The sync script mirrors githooks/, skills/, plugins/ and every src/ module into
34
+ * the package. Seeding the whole of `src/` except the package itself keeps this
35
+ * list from rotting: syncModules() gains a module, this helper needs no edit.
36
+ * Only reads from the real checkout -- nothing is ever written back to it.
37
+ */
38
+ function seedCanonicalSources(realRoot, destRoot) {
39
+ const real = realRoot || path.resolve(__dirname, '..', '..', '..', '..');
40
+ const dest = destRoot;
41
+ for (const tree of ['githooks', 'skills', 'plugins']) {
42
+ const from = path.join(real, tree);
43
+ if (fs.existsSync(from)) fs.cpSync(from, path.join(dest, tree), { recursive: true });
44
+ }
45
+ const srcRoot = path.join(real, 'src');
46
+ for (const name of fs.readdirSync(srcRoot)) {
47
+ // The package itself is the DESTINATION of the sync, not a canonical source;
48
+ // copying it would let the test compare a mirror against a stale mirror.
49
+ if (name === 'npm-package') continue;
50
+ const from = path.join(srcRoot, name);
51
+ if (fs.statSync(from).isDirectory()) {
52
+ fs.cpSync(from, path.join(dest, 'src', name), { recursive: true });
53
+ }
54
+ }
55
+ fs.mkdirSync(path.join(dest, 'src', 'npm-package', 'scripts'), { recursive: true });
56
+ for (const rel of ['scripts/sync-package-content.js', 'package.json']) {
57
+ fs.copyFileSync(path.join(srcRoot, 'npm-package', rel), path.join(dest, 'src', 'npm-package', rel));
58
+ }
59
+ }
60
+
30
61
  describe('checkDocsDrift', () => {
31
62
  let tempDir;
32
63
  let preCommitPath;
@@ -269,12 +300,56 @@ describe('syncGateScripts', () => {
269
300
  * @covers AC-450-01, AC-450-02, AC-450-03, AC-450-04
270
301
  */
271
302
  describe('syncHooks', () => {
272
- const WORKTREE_PATH = path.resolve(__dirname, '..', '..', '..', '..');
303
+ // #645: run the sync against a THROWAWAY worktree, never the checkout.
304
+ //
305
+ // This block used to spawn the real sync script with
306
+ // cwd: path.resolve(__dirname, '..', '..', '..', '..')
307
+ // -- four levels up from src/npm-package/scripts/__tests__ is the REPOSITORY
308
+ // ROOT, not a temp dir -- so the script's writes landed in the repo and CI's
309
+ // Gate 5 failed at vitest-worktree-guard with
310
+ // Work tree polluted by the test run: M src/npm-package/mock-policy/gate-m3.ts
311
+ //
312
+ // The pollution was CONDITIONAL (the writes are idempotent, so it only dirties
313
+ // the tree when the checked-out commit carries a stale mirror), which made it
314
+ // look flaky and encouraged "just re-run it" -- hiding the cause. The guard
315
+ // catches the consequence; a temp cwd removes the cause.
316
+ const REAL_REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
317
+ const TMP_WORKTREE = fs.mkdtempSync(path.join(os.tmpdir(), 'xp-gate-sync-hooks-'));
318
+ const WORKTREE_PATH = TMP_WORKTREE;
273
319
  const PKG_ROOT = path.join(WORKTREE_PATH, 'src', 'npm-package');
274
320
  const HOOKS_DIR = path.join(PKG_ROOT, 'hooks');
275
321
  const syncScript = path.join(WORKTREE_PATH, 'src', 'npm-package', 'scripts', 'sync-package-content.js');
276
322
  const nodeExe = process.execPath;
277
323
 
324
+ /**
325
+ * Lay out just enough of the repo shape inside the temp root for the sync script
326
+ * to read its canonical sources and write its mirrors. Copies only the trees the
327
+ * script touches: githooks/ (the hook source of truth) and the packages it
328
+ * mirrors into. The REAL checkout is only ever READ.
329
+ */
330
+ function seedTempWorktree() {
331
+ seedCanonicalSources(REAL_REPO_ROOT, WORKTREE_PATH);
332
+ fs.mkdirSync(path.join(PKG_ROOT, 'scripts'), { recursive: true });
333
+ fs.copyFileSync(
334
+ path.join(REAL_REPO_ROOT, 'src', 'npm-package', 'scripts', 'sync-package-content.js'),
335
+ syncScript,
336
+ );
337
+ // The script resolves its package root relative to its own location, so it
338
+ // must sit at <temp>/src/npm-package/scripts/ exactly as in the real tree.
339
+ fs.copyFileSync(
340
+ path.join(REAL_REPO_ROOT, 'src', 'npm-package', 'package.json'),
341
+ path.join(PKG_ROOT, 'package.json'),
342
+ );
343
+ }
344
+
345
+ beforeAll(() => {
346
+ seedTempWorktree();
347
+ });
348
+
349
+ afterAll(() => {
350
+ fs.rmSync(TMP_WORKTREE, { recursive: true, force: true });
351
+ });
352
+
278
353
  // Ensure hooks are re-synced once before tests run; share the result.
279
354
  let syncOk = false;
280
355
  beforeAll(() => {
@@ -473,9 +548,41 @@ describe('checkAdapterDrift', () => {
473
548
  const { spawnSync } = require('child_process');
474
549
 
475
550
  describe('sync-package-content subprocess invocation', () => {
476
- const WORKTREE_PATH = path.resolve(__dirname, '..', '..', '..', '..');
551
+ // #645: same rule as the syncHooks block above -- a test may not write into the
552
+ // repository. `path.resolve(__dirname, '..', '..', '..', '..')` IS the repo root,
553
+ // so spawning the sync script with it as cwd copied mirrors over the checkout and
554
+ // CI's Gate 5 failed at vitest-worktree-guard.
555
+ //
556
+ // This block additionally DELETES package directories to prove the sync
557
+ // recreates them, which is precisely the operation that must never touch a real
558
+ // checkout. Everything therefore runs inside a throwaway tree seeded from the
559
+ // real sources (read-only).
560
+ const REAL_REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
561
+ const TMP_WORKTREE = fs.mkdtempSync(path.join(os.tmpdir(), 'xp-gate-sync-sub-'));
562
+ const WORKTREE_PATH = TMP_WORKTREE;
477
563
  const PKG_ROOT = path.join(WORKTREE_PATH, 'src', 'npm-package');
478
564
  const REPO_ROOT = path.join(WORKTREE_PATH);
565
+
566
+ function seedTempWorktree() {
567
+ seedCanonicalSources(REAL_REPO_ROOT, WORKTREE_PATH);
568
+ fs.mkdirSync(path.join(PKG_ROOT, 'scripts'), { recursive: true });
569
+ fs.copyFileSync(
570
+ path.join(REAL_REPO_ROOT, 'src', 'npm-package', 'scripts', 'sync-package-content.js'),
571
+ path.join(PKG_ROOT, 'scripts', 'sync-package-content.js'),
572
+ );
573
+ fs.copyFileSync(
574
+ path.join(REAL_REPO_ROOT, 'src', 'npm-package', 'package.json'),
575
+ path.join(PKG_ROOT, 'package.json'),
576
+ );
577
+ }
578
+
579
+ beforeAll(() => {
580
+ seedTempWorktree();
581
+ });
582
+
583
+ afterAll(() => {
584
+ fs.rmSync(TMP_WORKTREE, { recursive: true, force: true });
585
+ });
479
586
  const CORE_SKILLS = [
480
587
  'admin-template-guidelines',
481
588
  'delphi-review',
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** e583bcfb
4
+ **Commit:** 2ce96f13
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.1.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:** e583bcfb
4
+ **Commit:** 2ce96f13
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.1.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:** e583bcfb
4
+ **Commit:** 2ce96f13
5
5
  **Branch:** main
6
- **Version:** 0.34.1.0
6
+ **Version:** 0.35.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.