@boyingliu01/xp-gate 0.27.6 → 0.28.0

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 (104) 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 +13 -2
  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 +0 -0
  23. package/build-integrity/gate-10.ts +290 -263
  24. package/gate-10.sh +25 -6
  25. package/gate-12-file-hygiene.sh +35 -1
  26. package/gate-3.sh +78 -2
  27. package/gate-4.sh +0 -0
  28. package/gate-7.sh +0 -0
  29. package/gate-8.sh +0 -0
  30. package/gate-9.sh +13 -2
  31. package/hooks/adapter-common.sh +0 -0
  32. package/hooks/gate-10.sh +25 -6
  33. package/hooks/gate-12-file-hygiene.sh +35 -1
  34. package/hooks/gate-3.sh +78 -2
  35. package/hooks/gate-4.sh +0 -0
  36. package/hooks/gate-7.sh +0 -0
  37. package/hooks/gate-8.sh +0 -0
  38. package/hooks/gate-9.sh +13 -2
  39. package/hooks/lib/audit-sanitize.sh +51 -0
  40. package/hooks/lib/ccn-ratchet.sh +183 -0
  41. package/hooks/lib/docs-only.sh +49 -0
  42. package/hooks/post-merge +0 -0
  43. package/hooks/pre-commit +74 -15
  44. package/hooks/pre-push +19 -4
  45. package/hooks/sprint-gate.sh +0 -0
  46. package/lib/__tests__/sprint-audit.test.js +207 -207
  47. package/lib/gate-audit.ts +9 -4
  48. package/lib/mattpocock-license-verdict.json +1 -1
  49. package/lib/retro.js +27 -6
  50. package/lib/sprint-audit.js +235 -235
  51. package/mock-policy/AGENTS.md +3 -3
  52. package/mutation/AGENTS.md +3 -3
  53. package/package.json +1 -1
  54. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  55. package/plugins/claude-code/bin/delphi-review-guard.sh +0 -0
  56. package/plugins/claude-code/bin/xp-gate-check +0 -0
  57. package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
  58. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
  59. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  60. package/plugins/dsh/package.json +1 -1
  61. package/plugins/dsh/skills/delphi-review/AGENTS.md +3 -3
  62. package/plugins/dsh/skills/sprint-flow/AGENTS.md +3 -3
  63. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +3 -3
  64. package/plugins/opencode/package.json +1 -1
  65. package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
  66. package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
  67. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  68. package/plugins/qoder/bin/sprint-flow-guard.sh +0 -0
  69. package/plugins/qoder/bin/xp-gate-check +0 -0
  70. package/plugins/qoder/plugin.json +1 -1
  71. package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
  72. package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
  73. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  74. package/principles/AGENTS.md +3 -3
  75. package/principles/__tests__/many-exports-reexport.test.ts +40 -0
  76. package/principles/adapters/typescript.ts +61 -30
  77. package/principles/boy-scout.ts +22 -17
  78. package/scripts/delphi-external-review.cjs +195 -132
  79. package/skills/delphi-review/AGENTS.md +3 -3
  80. package/skills/sprint-flow/AGENTS.md +3 -3
  81. package/skills/test-specification-alignment/AGENTS.md +3 -3
  82. package/sprint-gate.sh +0 -0
  83. package/plugins/dsh/lib/command.d.ts +0 -32
  84. package/plugins/dsh/lib/command.d.ts.map +0 -1
  85. package/plugins/dsh/lib/command.js +0 -81
  86. package/plugins/dsh/lib/delphi-config.d.ts +0 -75
  87. package/plugins/dsh/lib/delphi-config.d.ts.map +0 -1
  88. package/plugins/dsh/lib/delphi-config.js +0 -183
  89. package/plugins/dsh/lib/delphi-run.d.ts +0 -24
  90. package/plugins/dsh/lib/delphi-run.d.ts.map +0 -1
  91. package/plugins/dsh/lib/delphi-run.js +0 -92
  92. package/plugins/dsh/lib/delphi.d.ts +0 -88
  93. package/plugins/dsh/lib/delphi.d.ts.map +0 -1
  94. package/plugins/dsh/lib/delphi.js +0 -161
  95. package/plugins/dsh/lib/dialect.d.ts +0 -24
  96. package/plugins/dsh/lib/dialect.d.ts.map +0 -1
  97. package/plugins/dsh/lib/dialect.js +0 -36
  98. package/plugins/dsh/lib/gate-runner.d.ts +0 -18
  99. package/plugins/dsh/lib/gate-runner.d.ts.map +0 -1
  100. package/plugins/dsh/lib/gate-runner.js +0 -46
  101. package/plugins/dsh/lib/index.d.ts +0 -5
  102. package/plugins/dsh/lib/index.d.ts.map +0 -1
  103. package/plugins/dsh/lib/index.js +0 -162
  104. package/plugins/dsh/node_modules/.vite/vitest/results.json +0 -1
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-18
4
- **Commit:** 17c3c043
3
+ **Generated:** 2026-10-19
4
+ **Commit:** 07d3036f
5
5
  **Branch:** main
6
- **Version:** 0.27.6.0
6
+ **Version:** 0.28.0.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
File without changes
File without changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.27.6",
3
+ "version": "0.28.0",
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
- **Generated:** 2026-10-18
4
- **Commit:** 17c3c043
3
+ **Generated:** 2026-10-19
4
+ **Commit:** 07d3036f
5
5
  **Branch:** main
6
- **Version:** 0.27.6.0
6
+ **Version:** 0.28.0.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
- **Generated:** 2026-10-18
4
- **Commit:** 17c3c043
3
+ **Generated:** 2026-10-19
4
+ **Commit:** 07d3036f
5
5
  **Branch:** main
6
- **Version:** 0.27.6.0
6
+ **Version:** 0.28.0.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
- **Generated:** 2026-10-18
4
- **Commit:** 17c3c043
3
+ **Generated:** 2026-10-19
4
+ **Commit:** 07d3036f
5
5
  **Branch:** main
6
- **Version:** 0.27.6.0
6
+ **Version:** 0.28.0.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
- **Generated:** 2026-10-18
4
- **Commit:** 17c3c043
3
+ **Generated:** 2026-10-19
4
+ **Commit:** 07d3036f
5
5
  **Branch:** main
6
- **Version:** 0.27.6.0
6
+ **Version:** 0.28.0.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,40 @@
1
+ /**
2
+ * @test REQ-CCLINT-001
3
+ * @intent a pure re-export barrel (export { … } from './x') forwards names but
4
+ * adds no module surface; counting each forwarded member inflates the
5
+ * many-exports metric and blocks Boy-Scout splits that are exactly what
6
+ * the rule asks for (common.ts → common-process.ts)
7
+ * @covers AC-CCLINT-001-01
8
+ */
9
+ import { describe, it, expect, vi } from 'vitest';
10
+ import { readFileSync } from 'fs';
11
+ import { TypeScriptAdapter } from '../adapters/typescript';
12
+
13
+ vi.mock('fs', () => ({ readFileSync: vi.fn() }));
14
+
15
+ function exportCount(content: string): number {
16
+ (readFileSync as ReturnType<typeof vi.fn>).mockReturnValue(content);
17
+ return new TypeScriptAdapter('t.ts').extractExports().length;
18
+ }
19
+
20
+ describe('extractExports counts module surface, not forwarded names', () => {
21
+ it('does not count members of an export-from re-export block', () => {
22
+ const content = [
23
+ "export {",
24
+ ' type ToolAvailability,',
25
+ ' isToolAvailable,',
26
+ ' runTool,',
27
+ " getChangedFiles,",
28
+ "} from './common-process';",
29
+ 'export function local(): void {}',
30
+ '',
31
+ ].join('\n');
32
+ // One local export only — the six forwarded names are not this module's surface.
33
+ expect(exportCount(content)).toBe(1);
34
+ });
35
+
36
+ it('still counts local export { A, B } blocks without a from clause', () => {
37
+ const content = ['export { alpha, beta };', 'const alpha = 1;', 'const beta = 2;', ''].join('\n');
38
+ expect(exportCount(content)).toBe(2);
39
+ });
40
+ });
@@ -36,49 +36,68 @@ const CONTROL_KEYWORDS = new Set([
36
36
  'case', 'break', 'continue', 'const', 'let', 'var', 'as', 'satisfies', 'keyof',
37
37
  ]);
38
38
 
39
- /**
40
- * Find the index of the `{` that opens a declaration body for the parameter
41
- * list starting at `parenIndex`.
42
- *
43
- * Everything between the closing `)` and the body is scanned with a small depth
44
- * tracker so that a `{` belonging to a type literal in the return annotation
45
- * (`extract(): Array<{value: number}> {`) is not mistaken for the body. The
46
- * body brace is the first `{` seen at depth 0.
47
- */
39
+ // ---------------------------------------------------------------------------
40
+ // Lizard parse note: these two helpers are laid out so that no declaration
41
+ // carrying a return type annotation is immediately followed by a JSDoc block.
42
+ // lizard 1.21.3 mis-scopes that combination and reports the pairing as one row
43
+ // spanning every following function (measured here: findBodyBrace swallowed the
44
+ // rest of the file at CCN 69). Keep the plain comment style above them.
45
+ // ---------------------------------------------------------------------------
46
+
47
+ // Advance one scan character against the type-literal depth. Returns the new
48
+ // depth plus the index to resume from, so parenthesised groups are skipped whole.
49
+ // The inline typed-return form is avoided deliberately: lizard 1.21.3 loses the
50
+ // function boundary when a return annotation contains a brace literal.
51
+ type ScanStep = { depth: number; resume: number };
52
+
53
+ const stepDepth = (masked: string, c: string, i: number, depth: number): ScanStep => {
54
+ if (c === '(') {
55
+ const { end } = readParenGroup(masked, i);
56
+ return { depth, resume: end - 1 };
57
+ }
58
+ if (c === '<' || c === '[') return { depth: depth + 1, resume: i };
59
+ if (c === '>' || c === ']') return { depth: Math.max(0, depth - 1), resume: i };
60
+ if (c === '}') return { depth: Math.max(0, depth - 1), resume: i };
61
+ return { depth, resume: i };
62
+ };
63
+
48
64
  function findBodyBrace(masked: string, parenIndex: number): number {
49
65
  if (masked[parenIndex] !== '(') return -1;
50
66
  const { end } = readParenGroup(masked, parenIndex);
51
67
  let depth = 0;
52
- for (let i = end; i < masked.length; i++) {
68
+ let i = end;
69
+ while (i < masked.length) {
53
70
  const c = masked[i];
54
- if (c === '(') { const g = readParenGroup(masked, i); i = g.end - 1; continue; }
55
- if (c === '<' || c === '[') { depth++; continue; }
56
- if (c === '>' || c === ']') { if (depth > 0) depth--; continue; }
57
71
  if (c === '{') {
58
72
  if (depth === 0) return i;
59
73
  depth++;
74
+ i++;
60
75
  continue;
61
76
  }
62
- if (c === '}') { if (depth > 0) depth--; continue; }
63
- if (c === ';' || c === '\n') {
64
- // A line break before any `{` means this header has no braced body
65
- // (e.g. an interface member or an overload signature).
66
- if (depth === 0) return -1;
67
- }
77
+ // A line break or semicolon before any brace means this header has no
78
+ // braced body (an interface member or an overload signature).
79
+ if ((c === ';' || c === '\n') && depth === 0) return -1;
80
+ const stepped = stepDepth(masked, c, i, depth);
81
+ depth = stepped.depth;
82
+ i = stepped.resume + 1;
68
83
  }
69
84
  return -1;
70
85
  }
71
86
 
72
- /** Index just past the matching `}` for the block opened at `open`. */
87
+ // Index just past the matching closing brace for the block opened at open.
88
+ // Written with explicit braces throughout: an unbraced else-if chain here made
89
+ // lizard 1.21.3 extend the PREVIOUS function's span over this whole region.
73
90
  function findBlockEnd(masked: string, open: number): number {
74
91
  let depth = 0;
75
92
  for (let i = open; i < masked.length; i++) {
76
93
  const c = masked[i];
77
- if (c === '{') depth++;
78
- else if (c === '}') {
79
- depth--;
80
- if (depth === 0) return i + 1;
94
+ if (c === '{') {
95
+ depth++;
96
+ continue;
81
97
  }
98
+ if (c !== '}') continue;
99
+ depth--;
100
+ if (depth === 0) return i + 1;
82
101
  }
83
102
  return masked.length;
84
103
  }
@@ -651,14 +670,26 @@ export class TypeScriptAdapter extends BaseAdapter implements Adapter {
651
670
  });
652
671
  }
653
672
 
654
- const reExportRegex = /^export\s*{/gm;
673
+ // A re-export block lists names this module FORWARDS, not names it
674
+ // defines. Counting every forwarded member punishes exactly the barrel
675
+ // split (many-exports → sub-module + `export { … } from`) that the rule
676
+ // recommends, so only local `export { A, B };` blocks count — and each
677
+ // counts once per exported name, as before for single-line blocks.
678
+ const reExportRegex = /^export\s*\{([^}]*)\}(\s+from\s+[^;{]+)?;/gm;
655
679
  let reMatch;
656
680
  while ((reMatch = reExportRegex.exec(this.fileContent)) !== null) {
657
- exportMatches.push({
658
- name: reMatch[0],
659
- type: 're-export',
660
- line: this.getLineNumber(reMatch.index)
661
- });
681
+ if (reMatch[2]) continue; // `export { … } from '…'` forwards: no new surface
682
+ const names = reMatch[1]
683
+ .split(',')
684
+ .map((part) => part.replace(/^type\s+/, '').trim())
685
+ .filter((part) => part.length > 0);
686
+ for (const name of names) {
687
+ exportMatches.push({
688
+ name,
689
+ type: 're-export',
690
+ line: this.getLineNumber(reMatch.index),
691
+ });
692
+ }
662
693
  }
663
694
 
664
695
  return exportMatches;
@@ -147,6 +147,11 @@ export function classifyFiles(gitDiffLines: string[]): FileClassification {
147
147
  * cannot be trusted is worse than no baseline, because the gate still reports
148
148
  * "PASSED" and nobody looks.
149
149
  */
150
+ /** True when a stored budget is usable: a real, finite number. */
151
+ function hasFiniteBudget(entry: BaselineEntry): boolean {
152
+ return typeof entry.totalWarnings === 'number' && Number.isFinite(entry.totalWarnings);
153
+ }
154
+
150
155
  function assertValidEntry(file: string, entry: unknown): asserts entry is BaselineEntry {
151
156
  const malformed = (detail: string): never => {
152
157
  throw new Error(
@@ -159,8 +164,8 @@ function assertValidEntry(file: string, entry: unknown): asserts entry is Baseli
159
164
  if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) {
160
165
  malformed(`got ${Array.isArray(entry) ? 'an array' : typeof entry}`);
161
166
  }
162
- const total = (entry as BaselineEntry).totalWarnings;
163
- if (typeof total !== 'number' || !Number.isFinite(total)) {
167
+ if (!hasFiniteBudget(entry as BaselineEntry)) {
168
+ const total = (entry as BaselineEntry).totalWarnings;
164
169
  malformed(`totalWarnings is ${total === undefined ? 'missing' : String(total)}`);
165
170
  }
166
171
  }
@@ -190,22 +195,26 @@ export async function loadBaseline(baselinePath: string): Promise<Record<string,
190
195
  throw new Error(`Cannot read baseline ${baselinePath}: ${reason}`);
191
196
  }
192
197
 
198
+ const parsed = parseBaselineObject(baselineContent, baselinePath);
199
+ for (const [file, entry] of Object.entries(parsed)) {
200
+ assertValidEntry(file, entry);
201
+ }
202
+ return parsed as Record<string, BaselineEntry>;
203
+ }
204
+
205
+ /** Parse baseline JSON into an object map, or throw a located error. */
206
+ function parseBaselineObject(content: string, baselinePath: string): Record<string, unknown> {
193
207
  let parsed: unknown;
194
208
  try {
195
- parsed = JSON.parse(baselineContent);
209
+ parsed = JSON.parse(content);
196
210
  } catch (error) {
197
211
  const reason = error instanceof Error ? error.message : String(error);
198
212
  throw new Error(`Baseline ${baselinePath} is not valid JSON: ${reason}`);
199
213
  }
200
-
201
214
  if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
202
215
  throw new Error(`Baseline ${baselinePath} must be a JSON object mapping paths to entries.`);
203
216
  }
204
-
205
- for (const [file, entry] of Object.entries(parsed as Record<string, unknown>)) {
206
- assertValidEntry(file, entry);
207
- }
208
- return parsed as Record<string, BaselineEntry>;
217
+ return parsed as Record<string, unknown>;
209
218
  }
210
219
 
211
220
  /**
@@ -254,17 +263,13 @@ async function readBaselineText(baselinePath: string): Promise<string | null | {
254
263
 
255
264
  /** Parse the baseline JSON. Returns the object, or a human-readable failure. */
256
265
  function parseBaselineJson(content: string, baselinePath: string): Record<string, unknown> | string {
257
- let parsed: unknown;
266
+ // Same grammar as parseBaselineObject, reported instead of thrown: the
267
+ // repair path must keep going with an empty set where loadBaseline aborts.
258
268
  try {
259
- parsed = JSON.parse(content);
269
+ return parseBaselineObject(content, baselinePath);
260
270
  } catch (error) {
261
- const reason = error instanceof Error ? error.message : String(error);
262
- return `Baseline ${baselinePath} is not valid JSON: ${reason}`;
271
+ return error instanceof Error ? error.message : String(error);
263
272
  }
264
- if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
265
- return `Baseline ${baselinePath} must be a JSON object mapping paths to entries.`;
266
- }
267
- return parsed as Record<string, unknown>;
268
273
  }
269
274
 
270
275
  /** Split entries into the valid ones and the names of the malformed ones. */