@boyingliu01/xp-gate 0.35.24 → 0.36.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 (70) hide show
  1. package/adapter-common.sh +55 -2
  2. package/adapters/csharp.sh +245 -0
  3. package/adapters/gate-3.sh +1 -1
  4. package/adapters/gate-4.sh +1 -1
  5. package/adapters/gate-9.sh +37 -2
  6. package/adapters/iac.sh +45 -5
  7. package/bin/xp-gate.js +18 -11
  8. package/gate-10.sh +18 -3
  9. package/gate-13-compiler-strictness.sh +181 -0
  10. package/gate-3.sh +1 -1
  11. package/gate-4.sh +1 -1
  12. package/gate-9.sh +37 -2
  13. package/gates/common.ts +9 -2
  14. package/hooks/adapter-common.sh +55 -2
  15. package/hooks/gate-10.sh +18 -3
  16. package/hooks/gate-13-compiler-strictness.sh +181 -0
  17. package/hooks/gate-3.sh +1 -1
  18. package/hooks/gate-4.sh +1 -1
  19. package/hooks/gate-9.sh +37 -2
  20. package/hooks/gate-registry.sh +161 -0
  21. package/hooks/lib/test-failure.sh +29 -4
  22. package/hooks/pre-commit +122 -3
  23. package/lib/__tests__/agent-runtime-journal.test.js +42 -18
  24. package/lib/__tests__/check-single-file.test.js +55 -4
  25. package/lib/__tests__/detect-deps-dsh-profile.test.js +43 -0
  26. package/lib/__tests__/doctor-delphi.test.js +175 -0
  27. package/lib/__tests__/install-skill-full-dir.test.js +45 -2
  28. package/lib/__tests__/install-skill.test.js +84 -3
  29. package/lib/__tests__/mirror-sync.test.js +23 -5
  30. package/lib/__tests__/principles-csharp-coverage.test.js +227 -0
  31. package/lib/__tests__/uninstall-global-residue.test.js +192 -28
  32. package/lib/__tests__/update-skill-flags.test.js +78 -0
  33. package/lib/__tests__/update-skill.test.js +156 -3
  34. package/lib/detect-deps.js +24 -0
  35. package/lib/doctor.js +80 -12
  36. package/lib/install-skill.js +67 -18
  37. package/lib/mattpocock-license-verdict.json +1 -1
  38. package/lib/principles.js +9 -1
  39. package/lib/uninstall.js +96 -2
  40. package/lib/update-skill.js +143 -2
  41. package/mock-policy/AGENTS.md +4 -4
  42. package/mutation/AGENTS.md +4 -4
  43. package/package.json +1 -1
  44. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  45. package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
  46. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
  47. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
  48. package/plugins/dsh/lib/index.d.ts.map +1 -1
  49. package/plugins/dsh/lib/index.js +7 -4
  50. package/plugins/dsh/package.json +1 -1
  51. package/plugins/dsh/skills/delphi-review/AGENTS.md +4 -4
  52. package/plugins/dsh/skills/sprint-flow/AGENTS.md +4 -4
  53. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +4 -4
  54. package/plugins/dsh/src/index.test.ts +25 -0
  55. package/plugins/dsh/src/index.ts +7 -4
  56. package/plugins/opencode/package.json +1 -1
  57. package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
  58. package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
  59. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
  60. package/plugins/qoder/plugin.json +1 -1
  61. package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
  62. package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
  63. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
  64. package/principles/AGENTS.md +4 -4
  65. package/principles/adapters/csharp.ts +309 -0
  66. package/principles/analyzer.ts +5 -0
  67. package/scripts/delphi-external-review.cjs +130 -101
  68. package/skills/delphi-review/AGENTS.md +4 -4
  69. package/skills/sprint-flow/AGENTS.md +4 -4
  70. package/skills/test-specification-alignment/AGENTS.md +4 -4
@@ -561,10 +561,13 @@ export function apply(ctx: Context, config: unknown = {}): void {
561
561
  // delphi-review work on a machine whose key lives in the credential store rather
562
562
  // than in the host process's inherited environment.
563
563
  //
564
- // The cast is needed because the service is attached by a peer package's module
565
- // augmentation, which only applies when that package is in the compilation; a
566
- // consumer may install this plugin without it.
567
- const credentials = (ctx as unknown as { credentials?: CredentialProvider }).credentials
564
+ // Read via ctx.get() rather than a bare ctx.credentials (#721): Cordis's context
565
+ // proxy throws "cannot get property ... without inject" on any property that is
566
+ // neither a declared service nor in this plugin's inject list. "credentials" is
567
+ // deliberately NOT in inject — adding it would stop the plugin from loading on a
568
+ // host without the service, discarding the process.env fallback below. ctx.get()
569
+ // is the inject-free read and returns undefined when the host provides none.
570
+ const credentials: CredentialProvider | undefined = ctx.get?.("credentials")
568
571
  const resolveApiKey = createCredentialResolver(credentials, process.env)
569
572
  ctx.tools.register(gateCheckTool(shell, dialect))
570
573
  ctx.tools.register(gatePrinciplesTool(shell, dialect))
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/opencode-plugin",
3
- "version": "0.35.24",
3
+ "version": "0.36.1",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "description": "XP-Gate quality gates + AI workflow skills for OpenCode",
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-11-03
4
- **Commit:** fa007d1f
5
- **Branch:** main
6
- **Version:** 0.35.24.0
3
+ **Generated:** 2026-11-04
4
+ **Commit:** eb2097b0
5
+ **Branch:** sprint/2026-11-03-02
6
+ **Version:** 0.36.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
- **Generated:** 2026-11-03
4
- **Commit:** fa007d1f
5
- **Branch:** main
6
- **Version:** 0.35.24.0
3
+ **Generated:** 2026-11-04
4
+ **Commit:** eb2097b0
5
+ **Branch:** sprint/2026-11-03-02
6
+ **Version:** 0.36.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
- **Generated:** 2026-11-03
4
- **Commit:** fa007d1f
5
- **Branch:** main
6
- **Version:** 0.35.24.0
3
+ **Generated:** 2026-11-04
4
+ **Commit:** eb2097b0
5
+ **Branch:** sprint/2026-11-03-02
6
+ **Version:** 0.36.1.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.24",
3
+ "version": "0.36.1",
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-11-03
4
- **Commit:** fa007d1f
5
- **Branch:** main
6
- **Version:** 0.35.24.0
3
+ **Generated:** 2026-11-04
4
+ **Commit:** eb2097b0
5
+ **Branch:** sprint/2026-11-03-02
6
+ **Version:** 0.36.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
- **Generated:** 2026-11-03
4
- **Commit:** fa007d1f
5
- **Branch:** main
6
- **Version:** 0.35.24.0
3
+ **Generated:** 2026-11-04
4
+ **Commit:** eb2097b0
5
+ **Branch:** sprint/2026-11-03-02
6
+ **Version:** 0.36.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
- **Generated:** 2026-11-03
4
- **Commit:** fa007d1f
5
- **Branch:** main
6
- **Version:** 0.35.24.0
3
+ **Generated:** 2026-11-04
4
+ **Commit:** eb2097b0
5
+ **Branch:** sprint/2026-11-03-02
6
+ **Version:** 0.36.1.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-11-03
4
- **Commit:** fa007d1f
5
- **Branch:** main
6
- **Version:** 0.35.24.0
3
+ **Generated:** 2026-11-04
4
+ **Commit:** eb2097b0
5
+ **Branch:** sprint/2026-11-03-02
6
+ **Version:** 0.36.1.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,309 @@
1
+ import { BaseAdapter } from './base';
2
+ import { Adapter } from '../types';
3
+
4
+ /**
5
+ * Lexical state for the 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
+ * C# needs one more state than C++: verbatim strings (`@"..."`) terminate on a
9
+ * doubled quote rather than a backslash escape, and raw string literals
10
+ * (`"""..."""`) have no escape character at all. Treating either as a normal
11
+ * literal would mis-judge where the body ends.
12
+ */
13
+ interface CSharpLexState {
14
+ inString: boolean;
15
+ /** Which quote character opened the string (`'` or `"`). */
16
+ stringChar: string;
17
+ inBlockComment: boolean;
18
+ /** `@"..."`: `""` is an escaped quote, backslash is literal. */
19
+ inVerbatimString: boolean;
20
+ /** `"""..."""`: delimited by a run of >= 3 quotes. */
21
+ inRawString: boolean;
22
+ rawStringQuoteCount: number;
23
+ }
24
+
25
+ /**
26
+ * C# language adapter for the principles checker (Gate 4).
27
+ *
28
+ * Why this file exists: `analyzer.ts`'s `adapterMap` had no `.cs` entry, so
29
+ * `getAdapterForFile('A.cs')` returned `null`. `gate-4.sh` DID pass `.cs` paths
30
+ * to the checker (its extension filter includes `cs`), so the file was accepted
31
+ * and then dropped — a gate that printed "checking" and checked nothing. The
32
+ * AC that claimed Gate 4 covered C# by testing the *filter* alone was therefore
33
+ * wrong, which is what this adapter and its tests fix.
34
+ *
35
+ * Regex-based rather than AST-based, following `cpp.ts`: no C# parser is
36
+ * available in this zero-dependency package, and the same lexical-blindness
37
+ * problem (braces inside literals/comments) applies verbatim.
38
+ */
39
+ export class CSharpAdapter extends BaseAdapter implements Adapter {
40
+ detectLanguage(): string {
41
+ const lower = this.filePath.toLowerCase();
42
+ if (lower.endsWith('.cs') || lower.endsWith('.csx')) {
43
+ return 'csharp';
44
+ }
45
+ return super.detectLanguage();
46
+ }
47
+
48
+ parseAST(): unknown {
49
+ return this.createParseResult('csharp');
50
+ }
51
+
52
+ extractFunctions(): unknown[] {
53
+ const matches: unknown[] = [];
54
+
55
+ // C# method declaration: optional modifiers, return type, name, params,
56
+ // then either a block `{`, an expression body `=>`, or `;` (abstract /
57
+ // interface members). `async` and generics (`Task<int>`) must be tolerated
58
+ // or every async method in a modern codebase is invisible.
59
+ const methodRegex =
60
+ /(?:^|\n)[ \t]*(?:(?:public|private|protected|internal|static|async|override|virtual|sealed|abstract|extern|unsafe|new|partial)\s+)*(?:[\w.<>\[\],?]+\s+)?([A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*\(([^)]*)\)\s*(?:where\s[^{;]*)?(?:=>|\{|;)/g;
61
+
62
+ let match: RegExpExecArray | null;
63
+ while ((match = methodRegex.exec(this.fileContent)) !== null) {
64
+ const name = match[1];
65
+ // Constructors and `new` expressions look like the pattern above; the
66
+ // reserved words are not method names as far as the rules care.
67
+ if (CSharpAdapter.RESERVED.has(name)) continue;
68
+
69
+ const params = match[2].trim();
70
+ const paramCount = params === '' ? 0 : params.split(',').length;
71
+
72
+ matches.push(this.createCodeMatch(name, 'function', match.index));
73
+ const fnMatch = matches[matches.length - 1];
74
+ this.annotate(fnMatch, 'paramCount', paramCount);
75
+ this.annotateBodyMetrics(fnMatch);
76
+ }
77
+
78
+ // Constructors: `public Foo(...)` has no return type, so the regex above
79
+ // can miss it when the modifier list is empty or minimal.
80
+ const ctorRegex = /(?:^|\n)[ \t]*(?:(?:public|private|protected|internal|static)\s+)*([A-Za-z_]\w*)\s*\(([^)]*)\)\s*(?::\s*(?:base|this)\s*\([^)]*\)\s*)?\{/g;
81
+ while ((match = ctorRegex.exec(this.fileContent)) !== null) {
82
+ const name = match[1];
83
+ if (CSharpAdapter.RESERVED.has(name)) continue;
84
+ const params = match[2].trim();
85
+ const paramCount = params === '' ? 0 : params.split(',').length;
86
+ matches.push(this.createCodeMatch(name, 'constructor', match.index));
87
+ const ctorMatch = matches[matches.length - 1];
88
+ this.annotate(ctorMatch, 'paramCount', paramCount);
89
+ this.annotateBodyMetrics(ctorMatch);
90
+ }
91
+
92
+ return matches;
93
+ }
94
+
95
+ extractClasses(): unknown[] {
96
+ const matches: unknown[] = [];
97
+ // The trailing group accepts BOTH shapes a type declaration takes: a body
98
+ // (`{`) and a body-less primary-constructor record (`record Delta(int X);`,
99
+ // C# 9+). Requiring `{` made every such record invisible to the god-class /
100
+ // large-file rules.
101
+ const classRegex =
102
+ /\b(?:(?:public|private|protected|internal|static|sealed|abstract|partial|readonly|unsafe|new)\s+)*(class|interface|struct|record|enum)\s+([A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*(?:\([^)]*\))?\s*(?::\s*[^{\n;]+)?\s*(?:\{|;)/g;
103
+
104
+ let match: RegExpExecArray | null;
105
+ while ((match = classRegex.exec(this.fileContent)) !== null) {
106
+ matches.push(this.createCodeMatch(match[2], match[1], match.index));
107
+ const body = this.extractCodeBlock(match.index);
108
+ // methodCount feeds the god-class rule; counting declarations in the body
109
+ // is the only signal available without a real parser.
110
+ const methodCount = (body.match(/\b(?:public|private|protected|internal)\s+[^;{}]*\([^)]*\)\s*(?:=>|\{)/g) || []).length;
111
+ this.annotate(matches[matches.length - 1], 'methodCount', methodCount);
112
+ }
113
+
114
+ return matches;
115
+ }
116
+
117
+ /**
118
+ * Attach a computed metric to a match returned by `createCodeMatch`.
119
+ *
120
+ * The base helper builds `{name, type, line, code}`; the 14 rules read extra
121
+ * fields (length / paramCount / nestingDepth / methodCount / hasTryCatch), and
122
+ * a missing field is why 12 of those rules were structurally unable to fire on
123
+ * TypeScript before #446. This keeps the same lesson from repeating here.
124
+ */
125
+ private annotate(target: unknown, key: string, value: number): void {
126
+ (target as Record<string, unknown>)[key] = value;
127
+ }
128
+
129
+ /**
130
+ * Compute the body-derived metrics the rules read, from this match's own
131
+ * `code` (which `createCodeMatch` already cut with the literal-aware scanner).
132
+ *
133
+ * length / nestingDepth / hasTryCatch all come from the body, so computing
134
+ * them anywhere else would re-implement the brace scan and drift from it.
135
+ */
136
+ private annotateBodyMetrics(target: unknown): void {
137
+ const m = target as Record<string, unknown>;
138
+ const body = typeof m.code === 'string' ? m.code : '';
139
+
140
+ // Lines of the body, minus the declaration line itself.
141
+ const lineCount = body === '' ? 0 : body.split(/\r?\n/).length;
142
+ m.length = lineCount;
143
+
144
+ // Nesting depth = the deepest run of `{` that the scanner saw. Literals and
145
+ // comments were already removed by the scanner before this string was cut.
146
+ let depth = 0;
147
+ let maxDepth = 0;
148
+ for (const ch of body) {
149
+ if (ch === '{') { depth++; if (depth > maxDepth) maxDepth = depth; }
150
+ else if (ch === '}') depth--;
151
+ }
152
+ // A method body contributes its own level, so a flat body reads 1.
153
+ m.nestingDepth = maxDepth > 0 ? maxDepth : 0;
154
+
155
+ m.hasTryCatch = /\btry\s*\{/.test(body) && /\bcatch\s*[({]/.test(body);
156
+ }
157
+
158
+ /** Words that the C# grammar allows where a method name appears. */
159
+ private static readonly RESERVED = new Set([
160
+ 'if', 'for', 'foreach', 'while', 'switch', 'catch', 'using', 'lock', 'return',
161
+ 'new', 'throw', 'case', 'do', 'else', 'fixed', 'try', 'finally', 'yield',
162
+ 'await', 'typeof', 'sizeof', 'nameof', 'default', 'checked', 'unchecked',
163
+ ]);
164
+
165
+ override extractCodeBlock(startPos: number, maxFallback: number = 200): string {
166
+ const state = { inBlock: false, braceCount: 0 };
167
+ const lex: CSharpLexState = {
168
+ inString: false,
169
+ stringChar: '',
170
+ inBlockComment: false,
171
+ inVerbatimString: false,
172
+ inRawString: false,
173
+ rawStringQuoteCount: 0,
174
+ };
175
+ let endPos = startPos;
176
+
177
+ for (let i = startPos; i < this.fileContent.length;) {
178
+ const skip = this.lexSkip(i, lex);
179
+ if (skip > 0) { i += skip; continue; }
180
+
181
+ const result = this.advanceBraceTracker(this.fileContent[i], i, state);
182
+ if (result.closed) {
183
+ endPos = result.endPos;
184
+ break;
185
+ }
186
+ i++;
187
+ }
188
+
189
+ if (endPos > startPos) {
190
+ return this.fileContent.substring(startPos, endPos);
191
+ }
192
+
193
+ return this.fallbackCodeBlock(startPos, maxFallback);
194
+ }
195
+
196
+ /**
197
+ * Report how far the lexer can skip from `i`, updating `lex` as it goes.
198
+ *
199
+ * Same contract as `cpp.ts`'s `lexSkip`: returning 0 means the caller must
200
+ * inspect the character itself, which is what lets a `{` both end a
201
+ * literal-free run and open the block.
202
+ */
203
+ private lexSkip(i: number, lex: CSharpLexState): number {
204
+ const char = this.fileContent[i];
205
+ const nextChar = this.fileContent[i + 1] || '';
206
+
207
+ if (lex.inRawString) return this.skipRawStringBody(i, lex);
208
+ if (lex.inBlockComment) return this.skipBlockCommentBody(i, char, nextChar, lex);
209
+ if (lex.inString) return this.skipStringBody(i, char, lex);
210
+
211
+ if (char === '/' && nextChar === '/') return this.skipLineComment(i);
212
+ if (char === '/' && nextChar === '*') {
213
+ lex.inBlockComment = true;
214
+ return 2;
215
+ }
216
+
217
+ // `@"` opens a verbatim string; `"` alone opens a normal one.
218
+ if (char === '@' && nextChar === '"') {
219
+ lex.inString = true;
220
+ lex.inVerbatimString = true;
221
+ lex.stringChar = '"';
222
+ return 2;
223
+ }
224
+
225
+ // A run of 3+ quotes is a raw string literal.
226
+ if (char === '"') {
227
+ let run = 0;
228
+ let k = i;
229
+ while (this.fileContent[k] === '"') { run++; k++; }
230
+ if (run >= 3) {
231
+ lex.inRawString = true;
232
+ lex.rawStringQuoteCount = run;
233
+ return run;
234
+ }
235
+ lex.inString = true;
236
+ lex.inVerbatimString = false;
237
+ lex.stringChar = '"';
238
+ return 1;
239
+ }
240
+
241
+ if (char === "'") {
242
+ lex.inString = true;
243
+ lex.inVerbatimString = false;
244
+ lex.stringChar = "'";
245
+ return 1;
246
+ }
247
+
248
+ return 0;
249
+ }
250
+
251
+ /** Skip a `//` comment through, but not including, the newline. */
252
+ private skipLineComment(start: number): number {
253
+ let i = start;
254
+ while (i < this.fileContent.length && this.fileContent[i] !== '\n') i++;
255
+ return i - start;
256
+ }
257
+
258
+ private skipBlockCommentBody(
259
+ i: number,
260
+ char: string,
261
+ nextChar: string,
262
+ lex: CSharpLexState,
263
+ ): number {
264
+ if (char === '*' && nextChar === '/') {
265
+ lex.inBlockComment = false;
266
+ return 2;
267
+ }
268
+ return 1;
269
+ }
270
+
271
+ private skipStringBody(i: number, char: string, lex: CSharpLexState): number {
272
+ if (char !== lex.stringChar) return 1;
273
+
274
+ // Verbatim strings escape a quote by DOUBLING it: `@"a""b"` is `a"b`.
275
+ if (lex.inVerbatimString) {
276
+ if (this.fileContent[i + 1] === '"') return 2;
277
+ lex.inString = false;
278
+ lex.inVerbatimString = false;
279
+ return 1;
280
+ }
281
+
282
+ // A quote closes the literal only after an EVEN number of backslashes:
283
+ // `"\\"` is a backslash then a terminator, `"\""` is a quote as data.
284
+ let backslashCount = 0;
285
+ let j = i - 1;
286
+ while (j >= 0 && this.fileContent[j] === '\\') {
287
+ backslashCount++;
288
+ j--;
289
+ }
290
+ if (backslashCount % 2 === 0) lex.inString = false;
291
+ return 1;
292
+ }
293
+
294
+ /** A raw string closes on a quote run of the same length that opened it. */
295
+ private skipRawStringBody(i: number, lex: CSharpLexState): number {
296
+ if (this.fileContent[i] !== '"') return 1;
297
+
298
+ let run = 0;
299
+ let k = i;
300
+ while (this.fileContent[k] === '"') { run++; k++; }
301
+
302
+ if (run >= lex.rawStringQuoteCount) {
303
+ lex.inRawString = false;
304
+ lex.rawStringQuoteCount = 0;
305
+ return run;
306
+ }
307
+ return run;
308
+ }
309
+ }
@@ -9,6 +9,7 @@ import { DartAdapter } from './adapters/dart';
9
9
  import { SwiftAdapter } from './adapters/swift';
10
10
  import { ObjectiveCAdapter } from './adapters/objectivec';
11
11
  import { CppAdapter } from './adapters/cpp';
12
+ import { CSharpAdapter } from './adapters/csharp';
12
13
  import { extname } from 'path';
13
14
 
14
15
  export type { Violation } from './types';
@@ -63,6 +64,10 @@ export function getAdapterForFile(filePath: string): Adapter | null {
63
64
  '.swift': SwiftAdapter,
64
65
  '.m': ObjectiveCAdapter,
65
66
  '.mm': ObjectiveCAdapter,
67
+ // #717: without this entry `getAdapterForFile('A.cs')` returned null while
68
+ // gate-4.sh had already accepted the file, so Gate 4 inspected no C# at all.
69
+ '.cs': CSharpAdapter,
70
+ '.csx': CSharpAdapter,
66
71
  '.cpp': CppAdapter,
67
72
  '.cxx': CppAdapter,
68
73
  '.cc': CppAdapter,