@boyingliu01/xp-gate 0.31.3 → 0.32.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 (151) hide show
  1. package/adapters/java.sh +256 -4
  2. package/adapters/powershell.sh +45 -6
  3. package/bin/xp-gate.js +8 -2
  4. package/build-integrity/__tests__/gate-10.test.ts +88 -1
  5. package/build-integrity/gate-10.ts +51 -16
  6. package/dsh.bundle.patch.yml +15 -0
  7. package/gate-13-compiler-strictness.sh +1031 -0
  8. package/gate-3.sh +48 -13
  9. package/hooks/gate-13-compiler-strictness.sh +1031 -0
  10. package/hooks/gate-3.sh +48 -13
  11. package/hooks/lib/coverage-total.sh +18 -2
  12. package/hooks/lib/java-test-selection.sh +251 -0
  13. package/hooks/lib/jscpd-run.sh +9 -0
  14. package/hooks/lib/validate-code-walkthrough.cjs +186 -29
  15. package/hooks/pre-commit +269 -13
  16. package/lib/__tests__/detect-deps-dsh-profile.test.js +106 -0
  17. package/lib/__tests__/doctor-tui-skill-freshness.test.js +140 -0
  18. package/lib/__tests__/gate-runner.test.js +29 -2
  19. package/lib/__tests__/issue-586-principles-cmdline.test.js +35 -0
  20. package/lib/__tests__/issue-618-gate-not-run.test.js +100 -0
  21. package/lib/__tests__/shared-utils.test.js +21 -0
  22. package/lib/check.js +6 -2
  23. package/lib/detect-deps.js +42 -10
  24. package/lib/doctor-tui.js +111 -21
  25. package/lib/gate-runner.js +111 -37
  26. package/lib/mattpocock-license-verdict.json +1 -1
  27. package/lib/principles.js +48 -6
  28. package/lib/shared-utils.js +8 -0
  29. package/mock-policy/AGENTS.md +3 -3
  30. package/mock-policy/gate-m3.ts +57 -23
  31. package/mutation/AGENTS.md +3 -3
  32. package/mutation/__tests__/pitest-gradle-availability.test.ts +74 -0
  33. package/mutation/__tests__/test-filter-parity.test.ts +5 -2
  34. package/mutation/gate-m.ts +30 -24
  35. package/mutation/runners/mutmut-runner.ts +80 -30
  36. package/mutation/runners/pitest-runner.ts +60 -42
  37. package/package.json +14 -2
  38. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  39. package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
  40. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
  41. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  42. package/plugins/dsh/lib/command.d.ts +38 -0
  43. package/plugins/dsh/lib/command.d.ts.map +1 -0
  44. package/plugins/dsh/lib/command.js +87 -0
  45. package/plugins/dsh/lib/credentials.d.ts +39 -0
  46. package/plugins/dsh/lib/credentials.d.ts.map +1 -0
  47. package/plugins/dsh/lib/credentials.js +57 -0
  48. package/plugins/dsh/lib/delphi-config.d.ts +129 -0
  49. package/plugins/dsh/lib/delphi-config.d.ts.map +1 -0
  50. package/plugins/dsh/lib/delphi-config.js +369 -0
  51. package/plugins/dsh/lib/delphi-run.d.ts +24 -0
  52. package/plugins/dsh/lib/delphi-run.d.ts.map +1 -0
  53. package/plugins/dsh/lib/delphi-run.js +113 -0
  54. package/plugins/dsh/lib/delphi.d.ts +94 -0
  55. package/plugins/dsh/lib/delphi.d.ts.map +1 -0
  56. package/plugins/dsh/lib/delphi.js +207 -0
  57. package/plugins/dsh/lib/dialect.d.ts +24 -0
  58. package/plugins/dsh/lib/dialect.d.ts.map +1 -0
  59. package/plugins/dsh/lib/dialect.js +36 -0
  60. package/plugins/dsh/lib/evidence.d.ts +70 -0
  61. package/plugins/dsh/lib/evidence.d.ts.map +1 -0
  62. package/plugins/dsh/lib/evidence.js +92 -0
  63. package/plugins/dsh/lib/gate-runner.d.ts +49 -0
  64. package/plugins/dsh/lib/gate-runner.d.ts.map +1 -0
  65. package/plugins/dsh/lib/gate-runner.js +95 -0
  66. package/plugins/dsh/lib/git-context.d.ts +26 -0
  67. package/plugins/dsh/lib/git-context.d.ts.map +1 -0
  68. package/plugins/dsh/lib/git-context.js +30 -0
  69. package/plugins/dsh/lib/hooks.d.ts +42 -0
  70. package/plugins/dsh/lib/hooks.d.ts.map +1 -0
  71. package/plugins/dsh/lib/hooks.js +113 -0
  72. package/plugins/dsh/lib/index.d.ts +20 -0
  73. package/plugins/dsh/lib/index.d.ts.map +1 -0
  74. package/plugins/dsh/lib/index.js +430 -0
  75. package/plugins/dsh/lib/skill-provider.d.ts +15 -0
  76. package/plugins/dsh/lib/skill-provider.d.ts.map +1 -0
  77. package/plugins/dsh/lib/skill-provider.js +205 -0
  78. package/plugins/dsh/lib/sprint.d.ts +60 -0
  79. package/plugins/dsh/lib/sprint.d.ts.map +1 -0
  80. package/plugins/dsh/lib/sprint.js +92 -0
  81. package/plugins/dsh/package.json +14 -8
  82. package/plugins/dsh/scripts/verify-skill-provider.mjs +113 -0
  83. package/plugins/dsh/skills/delphi-review/AGENTS.md +3 -3
  84. package/plugins/dsh/skills/sprint-flow/AGENTS.md +3 -3
  85. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +3 -3
  86. package/plugins/dsh/src/command-single-form.test.ts +67 -0
  87. package/plugins/dsh/src/command.test.ts +21 -17
  88. package/plugins/dsh/src/command.ts +19 -21
  89. package/plugins/dsh/src/compat.test.ts +200 -0
  90. package/plugins/dsh/src/contract.test.ts +7 -1
  91. package/plugins/dsh/src/credentials.test.ts +93 -0
  92. package/plugins/dsh/src/credentials.ts +71 -0
  93. package/plugins/dsh/src/delphi-cancellation.test.ts +71 -0
  94. package/plugins/dsh/src/delphi-config-apikey-override.test.ts +94 -0
  95. package/plugins/dsh/src/delphi-config-credential-refs.test.ts +75 -0
  96. package/plugins/dsh/src/delphi-config.ts +146 -53
  97. package/plugins/dsh/src/delphi-run.ts +27 -4
  98. package/plugins/dsh/src/delphi.ts +133 -65
  99. package/plugins/dsh/src/evidence.test.ts +74 -0
  100. package/plugins/dsh/src/evidence.ts +141 -0
  101. package/plugins/dsh/src/gate-runner.test.ts +14 -6
  102. package/plugins/dsh/src/gate-runner.ts +68 -5
  103. package/plugins/dsh/src/git-context.test.ts +39 -0
  104. package/plugins/dsh/src/git-context.ts +38 -0
  105. package/plugins/dsh/src/hooks.test.ts +87 -0
  106. package/plugins/dsh/src/hooks.ts +126 -0
  107. package/plugins/dsh/src/index.test.ts +63 -6
  108. package/plugins/dsh/src/index.ts +340 -13
  109. package/plugins/dsh/src/skill-provider.test.ts +194 -0
  110. package/plugins/dsh/src/skill-provider.ts +231 -0
  111. package/plugins/dsh/src/sprint.test.ts +64 -0
  112. package/plugins/dsh/src/sprint.ts +122 -0
  113. package/plugins/dsh/src/tri-state.test.ts +123 -0
  114. package/plugins/opencode/index.ts +192 -58
  115. package/plugins/opencode/package.json +1 -1
  116. package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
  117. package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
  118. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  119. package/plugins/qoder/plugin.json +1 -1
  120. package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
  121. package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
  122. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  123. package/principles/AGENTS.md +3 -3
  124. package/principles/__tests__/analyze-read-failure.test.ts +70 -0
  125. package/principles/__tests__/baseline-entry-total-warnings.test.ts +76 -0
  126. package/principles/__tests__/baseline-prune.test.ts +61 -0
  127. package/principles/__tests__/cli-files-separators.test.ts +69 -0
  128. package/principles/__tests__/config-merge-warnings.test.ts +148 -0
  129. package/principles/adapters/__tests__/cpp-code-block.test.ts +123 -0
  130. package/principles/adapters/__tests__/cpp-scanner-equivalence.test.ts +67 -0
  131. package/principles/adapters/__tests__/typescript-block-comment-lines.test.ts +53 -0
  132. package/principles/adapters/__tests__/typescript-masking.test.ts +96 -0
  133. package/principles/adapters/cpp.ts +102 -50
  134. package/principles/adapters/java.ts +22 -3
  135. package/principles/adapters/typescript.ts +548 -178
  136. package/principles/analyzer.ts +36 -3
  137. package/principles/baseline-prune.ts +19 -7
  138. package/principles/baseline.ts +58 -70
  139. package/principles/config.ts +56 -35
  140. package/principles/index.ts +122 -40
  141. package/principles/rules/clean-code/deep-nesting.ts +36 -22
  142. package/principles/rules/clean-code/unused-imports.ts +30 -19
  143. package/principles/rules/solid/dip.ts +30 -20
  144. package/principles/rules/solid/lsp.ts +17 -2
  145. package/principles/rules/solid/ocp.ts +33 -23
  146. package/principles/rules/solid/srp.ts +65 -23
  147. package/scripts/delphi-external-review.cjs +25 -2
  148. package/scripts/sync-package-content.js +28 -1
  149. package/skills/delphi-review/AGENTS.md +3 -3
  150. package/skills/sprint-flow/AGENTS.md +3 -3
  151. package/skills/test-specification-alignment/AGENTS.md +3 -3
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @test REQ-620
3
+ * @intent Pin the behaviour of the literal/comment masker before it is
4
+ * refactored. The masker is a four-state character scanner (line
5
+ * comment, block comment, regex literal, string/template) whose output
6
+ * feeds EVERY metric the rule set reads: a masking mistake changes
7
+ * reported nesting depth, function length and body text for the whole
8
+ * adapter. These cases are the safety net for the CCN-36 split, so they
9
+ * assert observable masking outcomes (offsets preserved, literals
10
+ * blanked, structure kept) rather than any internal helper shape.
11
+ * @covers AC-620-01
12
+ *
13
+ * Masking contract (from the function's own docblock):
14
+ * - every comment / string / template / regex literal becomes spaces
15
+ * - offsets and newlines are preserved exactly (length and line count equal)
16
+ * - everything else is left byte-identical
17
+ */
18
+ import { describe, it, expect, vi } from 'vitest';
19
+ import { TypeScriptAdapter } from '../typescript';
20
+
21
+ vi.mock('fs', () => ({
22
+ readFileSync: vi.fn(),
23
+ existsSync: vi.fn(() => true),
24
+ }));
25
+
26
+ import { readFileSync } from 'fs';
27
+
28
+ /** Mask a source string by round-tripping it through the adapter's parser. */
29
+ function mask(src: string): string {
30
+ (readFileSync as unknown as { mockReturnValue: (v: string) => void }).mockReturnValue(src);
31
+ const adapter = new TypeScriptAdapter('probe.ts');
32
+ const ast = adapter.parseAST() as { masked?: string };
33
+ // The adapter exposes the masked source through a getter on the instance.
34
+ const masked = (adapter as unknown as { maskedSource?: string }).maskedSource;
35
+ return masked ?? ast.masked ?? '';
36
+ }
37
+
38
+ describe('AC-620-01: literal masking keeps offsets and blanks exactly the non-code', () => {
39
+ const cases: Array<[string, string]> = [
40
+ ['line comment', 'const a = 1; // hidden\nconst b = 2;'],
41
+ ['block comment', 'const a = 1; /* hidden\nstill hidden */ const b = 2;'],
42
+ ['single-quoted string', "const s = 'hidden text';"],
43
+ ['double-quoted string', 'const s = "hidden text";'],
44
+ ['template literal', 'const s = `hidden ${x} text`;'],
45
+ ['regex literal', 'const re = /hid+den/g;'],
46
+ ['escaped quote in string', "const s = 'it\\'s hidden';"],
47
+ ];
48
+
49
+ for (const [label, src] of cases) {
50
+ it(`preserves length and line count for a ${label}`, () => {
51
+ const out = mask(src);
52
+ expect(out.length).toBe(src.length);
53
+ expect(out.split('\n').length).toBe(src.split('\n').length);
54
+ });
55
+ }
56
+
57
+ it('blanks a line comment but keeps the code before it', () => {
58
+ const out = mask('const a = 1; // hidden');
59
+ expect(out).toContain('const a = 1;');
60
+ expect(out).not.toContain('hidden');
61
+ });
62
+
63
+ it('blanks a block comment across lines without eating the newline', () => {
64
+ const src = '/* a\nb */\nconst keep = 1;';
65
+ const out = mask(src);
66
+ expect(out).not.toContain('a\nb');
67
+ expect(out.split('\n').length).toBe(3);
68
+ expect(out).toContain('const keep = 1;');
69
+ });
70
+
71
+ it('blanks string contents and keeps the code around them', () => {
72
+ const out = mask('const s = "hidden"; const n = 42;');
73
+ expect(out).not.toContain('hidden');
74
+ expect(out).toContain('const s =');
75
+ expect(out).toContain('const n = 42;');
76
+ });
77
+
78
+ it('blanks a template literal including its interpolations', () => {
79
+ const out = mask('const s = `before ${x} after`;');
80
+ expect(out).not.toContain('before');
81
+ expect(out).not.toContain('after');
82
+ expect(out).toContain('const s =');
83
+ });
84
+
85
+ it('does not treat a division slash as the start of a regex literal', () => {
86
+ // `a / b` is division: the slash must not swallow the rest of the line.
87
+ const out = mask('const r = a / b; const keep = 1;');
88
+ expect(out).toContain('const keep = 1;');
89
+ expect(out).toContain('a / b');
90
+ });
91
+
92
+ it('leaves code with no literals byte-identical', () => {
93
+ const src = 'function f(a, b) {\n return a + b;\n}';
94
+ expect(mask(src)).toBe(src);
95
+ });
96
+ });
@@ -1,6 +1,17 @@
1
1
  import { BaseAdapter } from './base';
2
2
  import { Adapter } from '../types';
3
3
 
4
+ /**
5
+ * Lexical state for the C/C++ block scanner: which comment/quote the scan is
6
+ * currently inside. Braces seen in any of these states are data, not structure.
7
+ */
8
+ interface CppLexState {
9
+ inString: boolean;
10
+ /** Which quote character opened the string (`'` or `"`). */
11
+ stringChar: string;
12
+ inBlockComment: boolean;
13
+ }
14
+
4
15
  export class CppAdapter extends BaseAdapter implements Adapter {
5
16
  detectLanguage(): string {
6
17
  const ext = this.filePath.toLowerCase();
@@ -49,61 +60,102 @@ export class CppAdapter extends BaseAdapter implements Adapter {
49
60
  return classMatches;
50
61
  }
51
62
 
63
+ /**
64
+ * Report how far the lexer can skip from `i`, updating `lex` as it goes.
65
+ *
66
+ * C and C++ share three constructs the brace tracker must not see into: line
67
+ * comments, block comments, and quoted literals. A `}` inside any of them is
68
+ * data, so counting it would truncate the function body at the wrong place.
69
+ *
70
+ * Extracted from `extractCodeBlock`, which carried all three state machines
71
+ * plus the brace tracking in one CCN-28 body (#620).
72
+ *
73
+ * IMPORTANT: this reports a skip distance and never decides that a character
74
+ * is "consumed". Returning 0 means "the caller must look at this character
75
+ * itself" -- which is what lets a `{` both end a literal-free run and open the
76
+ * block. A `//` or `/*` opener IS skippable (the tracker never wants it), and
77
+ * so are quotes; braces are not.
78
+ *
79
+ * @returns the number of characters to skip, or 0 when the caller must inspect.
80
+ */
81
+ private lexSkip(i: number, lex: CppLexState): number {
82
+ const char = this.fileContent[i];
83
+ const nextChar = this.fileContent[i + 1] || '';
84
+
85
+ if (lex.inBlockComment) return this.skipBlockCommentBody(i, char, nextChar, lex);
86
+ if (lex.inString) return this.skipStringBody(i, char, lex);
87
+
88
+ if (char === '/' && nextChar === '/') return this.skipLineComment(i);
89
+ if (char === '/' && nextChar === '*') {
90
+ lex.inBlockComment = true;
91
+ return 2;
92
+ }
93
+ if (char === '"' || char === "'") {
94
+ lex.inString = true;
95
+ lex.stringChar = char;
96
+ return 1;
97
+ }
98
+ return 0;
99
+ }
100
+
101
+ /** Skip a `//` comment through, but not including, the newline. */
102
+ private skipLineComment(start: number): number {
103
+ let i = start;
104
+ while (i < this.fileContent.length && this.fileContent[i] !== '\n') i++;
105
+ return i - start;
106
+ }
107
+
108
+ private skipBlockCommentBody(
109
+ i: number,
110
+ char: string,
111
+ nextChar: string,
112
+ lex: CppLexState,
113
+ ): number {
114
+ if (char === '*' && nextChar === '/') {
115
+ lex.inBlockComment = false;
116
+ return 2;
117
+ }
118
+ return 1;
119
+ }
120
+
121
+ private skipStringBody(i: number, char: string, lex: CppLexState): number {
122
+ if (char !== lex.stringChar) return 1;
123
+ // A quote closes the literal only after an EVEN number of backslashes:
124
+ // `"\\"` is a backslash then a terminator, `"\""` is a quote as data.
125
+ let backslashCount = 0;
126
+ let j = i - 1;
127
+ while (j >= 0 && this.fileContent[j] === '\\') {
128
+ backslashCount++;
129
+ j--;
130
+ }
131
+ if (backslashCount % 2 === 0) lex.inString = false;
132
+ return 1;
133
+ }
134
+
52
135
  override extractCodeBlock(startPos: number, maxFallback: number = 200): string {
53
136
  const state = { inBlock: false, braceCount: 0 };
137
+ const lex: CppLexState = { inString: false, stringChar: '', inBlockComment: false };
54
138
  let endPos = startPos;
55
- let inString = false;
56
- let stringChar = '';
57
- let inComment = false;
58
-
59
- for (let i = startPos; i < this.fileContent.length; i++) {
60
- const char = this.fileContent[i];
61
- const nextChar = this.fileContent[i + 1] || '';
62
-
63
- if (!inString && !inComment) {
64
- if (char === '/' && nextChar === '/') {
65
- while (i < this.fileContent.length && this.fileContent[i] !== '\n') {
66
- i++;
67
- }
68
- continue;
69
- }
70
- if (char === '/' && nextChar === '*') {
71
- inComment = true;
72
- i++;
73
- continue;
74
- }
75
- } else if (inComment) {
76
- if (char === '*' && nextChar === '/') {
77
- inComment = false;
78
- i++;
79
- }
80
- continue;
81
- }
82
-
83
- if (!inComment) {
84
- if (!inString && (char === '"' || char === "'")) {
85
- inString = true;
86
- stringChar = char;
87
- } else if (inString && char === stringChar) {
88
- let backslashCount = 0;
89
- let j = i - 1;
90
- while (j >= 0 && this.fileContent[j] === '\\') {
91
- backslashCount++;
92
- j--;
93
- }
94
- if (backslashCount % 2 === 0) {
95
- inString = false;
96
- }
97
- }
98
- }
99
139
 
100
- if (!inString && !inComment) {
101
- const result = this.advanceBraceTracker(char, i, state);
102
- if (result.closed) {
103
- endPos = result.endPos;
104
- break;
105
- }
140
+ for (let i = startPos; i < this.fileContent.length;) {
141
+ // The lexer reports how far to skip, but must NEVER consume a character
142
+ // the brace tracker still has to see. A `{` both ends a literal-free run
143
+ // and opens the block; consuming it here would hide the function's own
144
+ // opening brace and leave every later brace off by one.
145
+ //
146
+ // `lexSkip` returns 0 ONLY for characters outside any literal or comment,
147
+ // so a positive skip already guarantees "not structural". There is
148
+ // deliberately no separate inString/inBlockComment test: it would be dead
149
+ // (verified by deleting it -- no test changes colour).
150
+ const skip = this.lexSkip(i, lex);
151
+ if (skip > 0) { i += skip; continue; }
152
+
153
+ const result = this.advanceBraceTracker(this.fileContent[i], i, state);
154
+ if (result.closed) {
155
+ endPos = result.endPos;
156
+ break;
106
157
  }
158
+ i++;
107
159
  }
108
160
 
109
161
  if (endPos > startPos) {
@@ -1,6 +1,19 @@
1
1
  import { BaseAdapter } from './base';
2
2
  import { Adapter } from '../types';
3
3
 
4
+ /**
5
+ * Depth change per nesting delimiter in a Java parameter list.
6
+ *
7
+ * Angular and square brackets nest exactly like parentheses here: the only thing
8
+ * that matters is whether a comma sits at depth 0, because that is the only
9
+ * place it separates parameters.
10
+ */
11
+ const PARAM_NESTING: Record<string, number> = {
12
+ '(': 1, ')': -1,
13
+ '<': 1, '>': -1,
14
+ '[': 1, ']': -1,
15
+ };
16
+
4
17
  /**
5
18
  * Structural fields the clean-code rules consume (mirror of the TypeScript
6
19
  * adapter's function shape). Before #507 this adapter returned only
@@ -213,12 +226,18 @@ export const javaParse = {
213
226
  countParams(paramList: string): number {
214
227
  const trimmed = paramList.trim();
215
228
  if (!trimmed) return 0;
229
+ // Generic and array brackets nest too: `Map<String, List<Integer>>` is ONE
230
+ // parameter, so the commas inside its arguments must not be counted.
231
+ // Which characters change depth and which separate is data, not control
232
+ // flow -- table lookups keep the scan to a single conditional.
216
233
  let depth = 0;
217
234
  let count = 1;
218
235
  for (const ch of trimmed) {
219
- if (ch === '(' || ch === '<' || ch === '[') depth += 1;
220
- else if (ch === ')' || ch === '>' || ch === ']') depth -= 1;
221
- else if (ch === ',' && depth === 0) count += 1;
236
+ if (ch === ',') {
237
+ if (depth === 0) count += 1;
238
+ continue;
239
+ }
240
+ depth += PARAM_NESTING[ch] ?? 0;
222
241
  }
223
242
  return count;
224
243
  },