truecourse 0.7.0-next.4 → 0.7.0-next.6

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 (94) hide show
  1. package/README.md +15 -3
  2. package/cli.mjs +1672 -749
  3. package/package.json +1 -1
  4. package/public/assets/{index-rfhWLMD4.js → index-xXZq2ACy.js} +232 -227
  5. package/public/index.html +1 -1
  6. package/roslyn-host/Humanizer.dll +0 -0
  7. package/roslyn-host/Microsoft.Bcl.AsyncInterfaces.dll +0 -0
  8. package/roslyn-host/Microsoft.Build.Locator.dll +0 -0
  9. package/roslyn-host/Microsoft.CodeAnalysis.CSharp.Workspaces.dll +0 -0
  10. package/roslyn-host/Microsoft.CodeAnalysis.CSharp.dll +0 -0
  11. package/roslyn-host/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll +0 -0
  12. package/roslyn-host/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll +0 -0
  13. package/roslyn-host/Microsoft.CodeAnalysis.Workspaces.dll +0 -0
  14. package/roslyn-host/Microsoft.CodeAnalysis.dll +0 -0
  15. package/roslyn-host/Microsoft.Extensions.DependencyInjection.Abstractions.dll +0 -0
  16. package/roslyn-host/Microsoft.Extensions.Logging.Abstractions.dll +0 -0
  17. package/roslyn-host/Newtonsoft.Json.dll +0 -0
  18. package/roslyn-host/System.Composition.AttributedModel.dll +0 -0
  19. package/roslyn-host/System.Composition.Convention.dll +0 -0
  20. package/roslyn-host/System.Composition.Hosting.dll +0 -0
  21. package/roslyn-host/System.Composition.Runtime.dll +0 -0
  22. package/roslyn-host/System.Composition.TypedParts.dll +0 -0
  23. package/roslyn-host/System.IO.Pipelines.dll +0 -0
  24. package/roslyn-host/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +0 -0
  25. package/roslyn-host/cs/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  26. package/roslyn-host/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +0 -0
  27. package/roslyn-host/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll +0 -0
  28. package/roslyn-host/cs/Microsoft.CodeAnalysis.resources.dll +0 -0
  29. package/roslyn-host/csharp-roslyn-host.deps.json +545 -0
  30. package/roslyn-host/csharp-roslyn-host.dll +0 -0
  31. package/roslyn-host/csharp-roslyn-host.dll.config +623 -0
  32. package/roslyn-host/csharp-roslyn-host.pdb +0 -0
  33. package/roslyn-host/csharp-roslyn-host.runtimeconfig.json +16 -0
  34. package/roslyn-host/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +0 -0
  35. package/roslyn-host/de/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  36. package/roslyn-host/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +0 -0
  37. package/roslyn-host/de/Microsoft.CodeAnalysis.Workspaces.resources.dll +0 -0
  38. package/roslyn-host/de/Microsoft.CodeAnalysis.resources.dll +0 -0
  39. package/roslyn-host/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +0 -0
  40. package/roslyn-host/es/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  41. package/roslyn-host/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +0 -0
  42. package/roslyn-host/es/Microsoft.CodeAnalysis.Workspaces.resources.dll +0 -0
  43. package/roslyn-host/es/Microsoft.CodeAnalysis.resources.dll +0 -0
  44. package/roslyn-host/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +0 -0
  45. package/roslyn-host/fr/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  46. package/roslyn-host/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +0 -0
  47. package/roslyn-host/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll +0 -0
  48. package/roslyn-host/fr/Microsoft.CodeAnalysis.resources.dll +0 -0
  49. package/roslyn-host/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +0 -0
  50. package/roslyn-host/it/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  51. package/roslyn-host/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +0 -0
  52. package/roslyn-host/it/Microsoft.CodeAnalysis.Workspaces.resources.dll +0 -0
  53. package/roslyn-host/it/Microsoft.CodeAnalysis.resources.dll +0 -0
  54. package/roslyn-host/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +0 -0
  55. package/roslyn-host/ja/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  56. package/roslyn-host/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +0 -0
  57. package/roslyn-host/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll +0 -0
  58. package/roslyn-host/ja/Microsoft.CodeAnalysis.resources.dll +0 -0
  59. package/roslyn-host/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +0 -0
  60. package/roslyn-host/ko/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  61. package/roslyn-host/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +0 -0
  62. package/roslyn-host/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll +0 -0
  63. package/roslyn-host/ko/Microsoft.CodeAnalysis.resources.dll +0 -0
  64. package/roslyn-host/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +0 -0
  65. package/roslyn-host/pl/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  66. package/roslyn-host/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +0 -0
  67. package/roslyn-host/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll +0 -0
  68. package/roslyn-host/pl/Microsoft.CodeAnalysis.resources.dll +0 -0
  69. package/roslyn-host/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +0 -0
  70. package/roslyn-host/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  71. package/roslyn-host/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +0 -0
  72. package/roslyn-host/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll +0 -0
  73. package/roslyn-host/pt-BR/Microsoft.CodeAnalysis.resources.dll +0 -0
  74. package/roslyn-host/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +0 -0
  75. package/roslyn-host/ru/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  76. package/roslyn-host/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +0 -0
  77. package/roslyn-host/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll +0 -0
  78. package/roslyn-host/ru/Microsoft.CodeAnalysis.resources.dll +0 -0
  79. package/roslyn-host/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +0 -0
  80. package/roslyn-host/tr/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  81. package/roslyn-host/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +0 -0
  82. package/roslyn-host/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll +0 -0
  83. package/roslyn-host/tr/Microsoft.CodeAnalysis.resources.dll +0 -0
  84. package/roslyn-host/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +0 -0
  85. package/roslyn-host/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  86. package/roslyn-host/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +0 -0
  87. package/roslyn-host/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll +0 -0
  88. package/roslyn-host/zh-Hans/Microsoft.CodeAnalysis.resources.dll +0 -0
  89. package/roslyn-host/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +0 -0
  90. package/roslyn-host/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  91. package/roslyn-host/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +0 -0
  92. package/roslyn-host/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll +0 -0
  93. package/roslyn-host/zh-Hant/Microsoft.CodeAnalysis.resources.dll +0 -0
  94. package/server.mjs +1224 -364
package/server.mjs CHANGED
@@ -28008,7 +28008,12 @@ var init_result = __esm({
28008
28008
  durationMs: external_exports.number().nonnegative(),
28009
28009
  /** Present on `fail` / `error`. */
28010
28010
  failure: GuardFailureDetailSchema.optional(),
28011
- /** Repo-relative pointer into `evidence/`; present on `fail` / `error`. */
28011
+ /**
28012
+ * Repo-relative pointer into `evidence/`; present on every EXECUTED outcome —
28013
+ * `pass` (the run transcript proving what ran) as well as `fail` / `error`.
28014
+ * Absent on non-executed `stale` / `orphaned`, and on pre-2026-07-08 runs whose
28015
+ * passes were never given a bundle (optional keeps those parsing + rendering).
28016
+ */
28012
28017
  evidencePath: external_exports.string().optional(),
28013
28018
  /**
28014
28019
  * Present when the bound section moved: the section kept its text but now
@@ -28404,6 +28409,153 @@ var init_guard = __esm({
28404
28409
  }
28405
28410
  });
28406
28411
 
28412
+ // packages/shared/dist/spec/overlap-resolution.js
28413
+ function coveringRelationInAreas(relations, a, b, areas) {
28414
+ return relations.find((r) => {
28415
+ const samePair = r.older === a && r.newer === b || r.older === b && r.newer === a;
28416
+ return samePair && (r.scope === void 0 || areas.includes(r.scope));
28417
+ });
28418
+ }
28419
+ function dedupeCrossAreaOverlaps(entries) {
28420
+ const byPair = /* @__PURE__ */ new Map();
28421
+ for (const e of entries) {
28422
+ const key4 = unorderedPairKey(e.overlap.docs[0], e.overlap.docs[1]);
28423
+ const list = byPair.get(key4);
28424
+ if (list)
28425
+ list.push(e);
28426
+ else
28427
+ byPair.set(key4, [e]);
28428
+ }
28429
+ const merged = [];
28430
+ for (const members of byPair.values()) {
28431
+ const parent = members.map((_, i) => i);
28432
+ const find = (i) => {
28433
+ while (parent[i] !== i) {
28434
+ parent[i] = parent[parent[i]];
28435
+ i = parent[i];
28436
+ }
28437
+ return i;
28438
+ };
28439
+ const union = (i, j) => {
28440
+ const ri = find(i);
28441
+ const rj = find(j);
28442
+ if (ri !== rj)
28443
+ parent[Math.max(ri, rj)] = Math.min(ri, rj);
28444
+ };
28445
+ const ptrOwner = /* @__PURE__ */ new Map();
28446
+ members.forEach((m, i) => {
28447
+ for (const ptr of sectionPointerKeys(m.overlap)) {
28448
+ const owner = ptrOwner.get(ptr);
28449
+ if (owner === void 0)
28450
+ ptrOwner.set(ptr, i);
28451
+ else
28452
+ union(owner, i);
28453
+ }
28454
+ });
28455
+ const components = /* @__PURE__ */ new Map();
28456
+ members.forEach((_, i) => {
28457
+ const root = find(i);
28458
+ const list = components.get(root);
28459
+ if (list)
28460
+ list.push(i);
28461
+ else
28462
+ components.set(root, [i]);
28463
+ });
28464
+ for (const idxs of components.values()) {
28465
+ const group = idxs.map((i) => members[i]);
28466
+ const rep = [...group].sort((x, y) => {
28467
+ const px = preambleCount(x.overlap);
28468
+ const py = preambleCount(y.overlap);
28469
+ if (px !== py)
28470
+ return px - py;
28471
+ if (x.area !== y.area)
28472
+ return x.area < y.area ? -1 : 1;
28473
+ return (x.overlap.note ?? "") < (y.overlap.note ?? "") ? -1 : 1;
28474
+ })[0];
28475
+ const span = /* @__PURE__ */ new Set();
28476
+ for (const g of group) {
28477
+ span.add(g.area);
28478
+ for (const a of g.overlap.areas ?? [])
28479
+ span.add(a);
28480
+ }
28481
+ merged.push({ area: rep.area, areas: [...span].sort(), overlap: rep.overlap });
28482
+ }
28483
+ }
28484
+ merged.sort((x, y) => {
28485
+ if (x.area !== y.area)
28486
+ return x.area < y.area ? -1 : 1;
28487
+ return unorderedPairKey(x.overlap.docs[0], x.overlap.docs[1]) < unorderedPairKey(y.overlap.docs[0], y.overlap.docs[1]) ? -1 : 1;
28488
+ });
28489
+ return merged;
28490
+ }
28491
+ function buildCorpusConflicts(corpus, decisions) {
28492
+ const userRelations = decisions.relations ?? [];
28493
+ const effectiveRels = [...corpus.relations, ...userRelations];
28494
+ const excludes = new Set(decisions.manualExcludes ?? []);
28495
+ const entries = [];
28496
+ for (const area of corpus.areas)
28497
+ for (const ov of area.overlaps)
28498
+ entries.push({ area: area.id, overlap: ov });
28499
+ const mergedOverlaps = dedupeCrossAreaOverlaps(entries);
28500
+ const flagged = [];
28501
+ for (const m of mergedOverlaps) {
28502
+ const [a, b] = m.overlap.docs;
28503
+ const relation = coveringRelationInAreas(effectiveRels, a, b, m.areas);
28504
+ const userRelation = coveringRelationInAreas(userRelations, a, b, m.areas);
28505
+ const excludedRef = excludes.has(a) ? a : excludes.has(b) ? b : void 0;
28506
+ flagged.push({
28507
+ area: m.area,
28508
+ areas: m.areas,
28509
+ a,
28510
+ b,
28511
+ note: m.overlap.note ?? "",
28512
+ resolved: relation !== void 0 || excludedRef !== void 0,
28513
+ synthesized: false,
28514
+ ...relation ? { relation } : {},
28515
+ ...userRelation ? { userRelation } : {},
28516
+ ...excludedRef ? { excludedRef } : {}
28517
+ });
28518
+ }
28519
+ const coversFlagged = (r) => flagged.some((o) => {
28520
+ const samePair = o.a === r.older && o.b === r.newer || o.a === r.newer && o.b === r.older;
28521
+ return samePair && (r.scope === void 0 || o.areas.includes(r.scope));
28522
+ });
28523
+ const synthesized = userRelations.filter((r) => !coversFlagged(r)).map((r) => ({
28524
+ area: r.scope ?? "",
28525
+ areas: r.scope ? [r.scope] : [],
28526
+ a: r.older,
28527
+ b: r.newer,
28528
+ note: r.note ?? "",
28529
+ resolved: true,
28530
+ synthesized: true,
28531
+ relation: r,
28532
+ userRelation: r
28533
+ }));
28534
+ return [...flagged, ...synthesized];
28535
+ }
28536
+ function openConflicts(corpus, decisions) {
28537
+ return buildCorpusConflicts(corpus, decisions).filter((c) => !c.resolved);
28538
+ }
28539
+ var PREAMBLE_PTR, NUL, unorderedPairKey, sectionPointerKeys, preambleCount;
28540
+ var init_overlap_resolution = __esm({
28541
+ "packages/shared/dist/spec/overlap-resolution.js"() {
28542
+ "use strict";
28543
+ PREAMBLE_PTR = "\0preamble";
28544
+ NUL = "\0";
28545
+ unorderedPairKey = (a, b) => a < b ? `${a}${NUL}${b}` : `${b}${NUL}${a}`;
28546
+ sectionPointerKeys = (ov) => (ov.sections ?? []).map((s) => `${s.doc}${NUL}${s.heading ?? PREAMBLE_PTR}`);
28547
+ preambleCount = (ov) => (ov.sections ?? []).filter((s) => s.heading === null || s.heading === void 0).length;
28548
+ }
28549
+ });
28550
+
28551
+ // packages/shared/dist/spec/index.js
28552
+ var init_spec = __esm({
28553
+ "packages/shared/dist/spec/index.js"() {
28554
+ "use strict";
28555
+ init_overlap_resolution();
28556
+ }
28557
+ });
28558
+
28407
28559
  // node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
28408
28560
  var require_ignore = __commonJS({
28409
28561
  "node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js"(exports, module) {
@@ -28994,6 +29146,7 @@ var init_dist = __esm({
28994
29146
  init_types2();
28995
29147
  init_schemas();
28996
29148
  init_guard();
29149
+ init_spec();
28997
29150
  init_tcignore();
28998
29151
  init_skip_dirs();
28999
29152
  init_spec_scope();
@@ -44192,8 +44345,8 @@ function getMaxParameters(filePath) {
44192
44345
  }
44193
44346
  return 5;
44194
44347
  }
44195
- function isBootstrapEntry(functionName, filePath) {
44196
- return LANGUAGE_CONFIGS.some((c) => c.bootstrap.functionNames.includes(functionName) && c.bootstrap.filePattern.test(filePath));
44348
+ function isBootstrapEntry(functionName2, filePath) {
44349
+ return LANGUAGE_CONFIGS.some((c) => c.bootstrap.functionNames.includes(functionName2) && c.bootstrap.filePattern.test(filePath));
44197
44350
  }
44198
44351
  function getLanguageConfig(language) {
44199
44352
  const config2 = LANGUAGE_CONFIGS.find((c) => c.name === language);
@@ -48928,9 +49081,6 @@ function buildCSharpSymbolIndex(files, rootPath) {
48928
49081
  }
48929
49082
  function contributeCSharpEdges(files, rootPath) {
48930
49083
  const index = buildCSharpSymbolIndex(files, rootPath);
48931
- if (index.stats.ambiguousRefs > 0) {
48932
- console.warn(`[TrueCourse] C# symbol index: ${index.stats.ambiguousRefs} ambiguous type reference(s) skipped (same type name visible from multiple namespaces)`);
48933
- }
48934
49084
  return index.edges;
48935
49085
  }
48936
49086
  var TYPE_DECL_KINDS;
@@ -54259,12 +54409,9 @@ var init_lsp_client = __esm({
54259
54409
  this.process.stdout.on("data", (chunk3) => this.onData(chunk3));
54260
54410
  this.process.stderr.on("data", (chunk3) => {
54261
54411
  });
54262
- this.process.on("error", (err) => {
54263
- for (const pending of this.pendingRequests.values()) {
54264
- pending.reject(err);
54265
- }
54266
- this.pendingRequests.clear();
54267
- });
54412
+ this.process.on("error", (err) => this.rejectAllPending(err));
54413
+ this.process.stdin.on("error", (err) => this.rejectAllPending(new Error(`${this.config.name} LSP stdin error: ${err.message}`)));
54414
+ this.process.on("exit", (code, signal) => this.rejectAllPending(new Error(`${this.config.name} LSP server exited (${signal ?? `code ${code}`}) before responding`)));
54268
54415
  const initResult = await this.sendRequest("initialize", {
54269
54416
  processId: process.pid,
54270
54417
  capabilities: {
@@ -54443,17 +54590,33 @@ var init_lsp_client = __esm({
54443
54590
  // -------------------------------------------------------------------------
54444
54591
  // Internal: JSON-RPC transport
54445
54592
  // -------------------------------------------------------------------------
54593
+ /** Reject and clear every in-flight request — used when the server dies. */
54594
+ rejectAllPending(err) {
54595
+ if (this.pendingRequests.size === 0)
54596
+ return;
54597
+ for (const pending of this.pendingRequests.values())
54598
+ pending.reject(err);
54599
+ this.pendingRequests.clear();
54600
+ }
54446
54601
  sendRequest(method, params) {
54447
54602
  return new Promise((resolve11, reject) => {
54448
54603
  const id = ++this.requestId;
54449
54604
  this.pendingRequests.set(id, { resolve: resolve11, reject });
54450
54605
  const msg = { jsonrpc: "2.0", id, method, params };
54451
- this.process.stdin.write(encodeMessage(msg));
54606
+ try {
54607
+ this.process.stdin.write(encodeMessage(msg));
54608
+ } catch (err) {
54609
+ this.pendingRequests.delete(id);
54610
+ reject(err instanceof Error ? err : new Error(String(err)));
54611
+ }
54452
54612
  });
54453
54613
  }
54454
54614
  sendNotification(method, params) {
54455
54615
  const msg = { jsonrpc: "2.0", method, params };
54456
- this.process.stdin.write(encodeMessage(msg));
54616
+ try {
54617
+ this.process.stdin.write(encodeMessage(msg));
54618
+ } catch {
54619
+ }
54457
54620
  }
54458
54621
  onData(chunk3) {
54459
54622
  this.buffer = Buffer.concat([this.buffer, chunk3]);
@@ -64938,7 +65101,8 @@ var init_deterministic4 = __esm({
64938
65101
  description: "Function parameter is never referenced in the function body \u2014 remove it or prefix with `_`.",
64939
65102
  enabled: true,
64940
65103
  severity: "low",
64941
- type: "deterministic"
65104
+ type: "deterministic",
65105
+ engine: "roslyn-host"
64942
65106
  },
64943
65107
  {
64944
65108
  key: "code-quality/deterministic/hardcoded-url",
@@ -76222,6 +76386,94 @@ var init_production_debug_enabled = __esm({
76222
76386
  });
76223
76387
 
76224
76388
  // packages/analyzer/dist/rules/security/visitors/javascript/insecure-random.js
76389
+ function isSecuritySensitiveName(name) {
76390
+ const lower = name.toLowerCase();
76391
+ return SECURITY_KEYWORDS.some((kw) => lower.includes(kw));
76392
+ }
76393
+ function securityContextName(callNode) {
76394
+ let parent = callNode.parent;
76395
+ while (parent) {
76396
+ switch (parent.type) {
76397
+ case "variable_declarator":
76398
+ return leafName(parent.childForFieldName("name"));
76399
+ case "assignment_expression":
76400
+ case "augmented_assignment_expression":
76401
+ return leafName(parent.childForFieldName("left"));
76402
+ case "pair":
76403
+ return leafName(parent.childForFieldName("key"));
76404
+ case "public_field_definition":
76405
+ case "field_definition":
76406
+ return leafName(parent.childForFieldName("property") ?? parent.childForFieldName("name"));
76407
+ // The value is returned from the enclosing function (e.g.
76408
+ // `function generateToken() { return Math.random()... }`) — the
76409
+ // function name is the security context.
76410
+ case "return_statement":
76411
+ return enclosingFunctionName(parent);
76412
+ // Expression-bodied arrow returns the value directly, e.g.
76413
+ // `const genToken = () => Math.random()...`.
76414
+ case "arrow_function":
76415
+ return functionName(parent);
76416
+ // Structural boundaries: the random value is discarded or otherwise
76417
+ // not bound to a name.
76418
+ case "expression_statement":
76419
+ case "statement_block":
76420
+ case "if_statement":
76421
+ case "for_statement":
76422
+ case "for_in_statement":
76423
+ case "while_statement":
76424
+ case "do_statement":
76425
+ case "switch_statement":
76426
+ case "function_declaration":
76427
+ case "function_expression":
76428
+ case "method_definition":
76429
+ return null;
76430
+ }
76431
+ parent = parent.parent;
76432
+ }
76433
+ return null;
76434
+ }
76435
+ function enclosingFunctionName(node2) {
76436
+ let p = node2.parent;
76437
+ while (p) {
76438
+ if (p.type === "function_declaration" || p.type === "generator_function_declaration" || p.type === "method_definition" || p.type === "arrow_function" || p.type === "function_expression") {
76439
+ return functionName(p);
76440
+ }
76441
+ p = p.parent;
76442
+ }
76443
+ return null;
76444
+ }
76445
+ function functionName(fn) {
76446
+ const nameField = fn.childForFieldName("name");
76447
+ if (nameField)
76448
+ return leafName(nameField);
76449
+ const p = fn.parent;
76450
+ if (!p)
76451
+ return null;
76452
+ if (p.type === "variable_declarator")
76453
+ return leafName(p.childForFieldName("name"));
76454
+ if (p.type === "pair")
76455
+ return leafName(p.childForFieldName("key"));
76456
+ if (p.type === "assignment_expression")
76457
+ return leafName(p.childForFieldName("left"));
76458
+ if (p.type === "public_field_definition" || p.type === "field_definition") {
76459
+ return leafName(p.childForFieldName("property") ?? p.childForFieldName("name"));
76460
+ }
76461
+ return null;
76462
+ }
76463
+ function leafName(node2) {
76464
+ if (!node2)
76465
+ return null;
76466
+ if (node2.type === "identifier" || node2.type === "property_identifier" || node2.type === "shorthand_property_identifier") {
76467
+ return node2.text;
76468
+ }
76469
+ if (node2.type === "member_expression") {
76470
+ return node2.childForFieldName("property")?.text ?? null;
76471
+ }
76472
+ if (node2.type === "string") {
76473
+ return node2.text.slice(1, -1);
76474
+ }
76475
+ return node2.text;
76476
+ }
76225
76477
  function isFixtureOrTestFile(filePath) {
76226
76478
  const lower = filePath.toLowerCase();
76227
76479
  return /[\\/]seeds?[\\/]/.test(lower) || /[\\/](?:seed|seeds)\.(?:ts|tsx|js|jsx|mts|cts|mjs|cjs)$/.test(lower) || /\.seed\.(?:ts|tsx|js|jsx|mts|cts|mjs|cjs)$/.test(lower) || /[\\/]__tests__[\\/]/.test(lower) || /\.(?:test|spec)\.(?:ts|tsx|js|jsx|mts|cts|mjs|cjs)$/.test(lower);
@@ -76252,19 +76504,9 @@ var init_insecure_random = __esm({
76252
76504
  if (/\.length\b/.test(parentText))
76253
76505
  return null;
76254
76506
  }
76255
- let parent = node2.parent;
76256
- while (parent) {
76257
- const parentText = parent.text.toLowerCase();
76258
- if (SECURITY_KEYWORDS.some((kw) => parentText.includes(kw))) {
76259
- const immediateParent = node2.parent;
76260
- if (immediateParent?.type === "binary_expression" && /\.length\b/.test(immediateParent.text)) {
76261
- return null;
76262
- }
76263
- return makeViolation(this.ruleKey, node2, filePath, "high", "Insecure random number generator", "Math.random() is not cryptographically secure. Do not use it for tokens, keys, or secrets.", sourceCode, "Use crypto.randomBytes() or crypto.randomUUID() instead.");
76264
- }
76265
- if (parent.type === "expression_statement" || parent.type === "variable_declaration" || parent.type === "assignment_expression" || parent.type === "lexical_declaration")
76266
- break;
76267
- parent = parent.parent;
76507
+ const contextName = securityContextName(node2);
76508
+ if (contextName && isSecuritySensitiveName(contextName)) {
76509
+ return makeViolation(this.ruleKey, node2, filePath, "high", "Insecure random number generator", "Math.random() is not cryptographically secure. Do not use it for tokens, keys, or secrets.", sourceCode, "Use crypto.randomBytes() or crypto.randomUUID() instead.");
76268
76510
  }
76269
76511
  }
76270
76512
  }
@@ -76693,6 +76935,11 @@ var init_unverified_cross_origin_message = __esm({
76693
76935
  }
76694
76936
  if (methodName3 !== "addEventListener")
76695
76937
  return null;
76938
+ if (fn.type === "member_expression") {
76939
+ const receiver = fn.childForFieldName("object")?.text ?? "";
76940
+ if (receiver !== "window" && receiver !== "self" && receiver !== "globalThis")
76941
+ return null;
76942
+ }
76696
76943
  const args = node2.childForFieldName("arguments");
76697
76944
  if (!args)
76698
76945
  return null;
@@ -77175,7 +77422,11 @@ var init_timing_attack_comparison = __esm({
77175
77422
  return null;
77176
77423
  const leftLeaf = leafIdentifier(left);
77177
77424
  const rightLeaf = leafIdentifier(right);
77178
- if (SENSITIVE_COMPARISON_PATTERNS.test(leftLeaf) || SENSITIVE_COMPARISON_PATTERNS.test(rightLeaf)) {
77425
+ const leftIsLiteral = left.type === "string" || left.type === "template_string";
77426
+ const rightIsLiteral = right.type === "string" || right.type === "template_string";
77427
+ const leftMatches = !leftIsLiteral && SENSITIVE_COMPARISON_PATTERNS.test(leftLeaf);
77428
+ const rightMatches = !rightIsLiteral && SENSITIVE_COMPARISON_PATTERNS.test(rightLeaf);
77429
+ if (leftMatches || rightMatches) {
77179
77430
  if (sourceCode.includes("timingSafeEqual"))
77180
77431
  return null;
77181
77432
  return makeViolation(this.ruleKey, node2, filePath, "medium", "Timing attack via string comparison", `Using ${operator.text} to compare what may be a secret/token. This is vulnerable to timing attacks.`, sourceCode, "Use crypto.timingSafeEqual() for comparing secrets and tokens.");
@@ -77762,6 +78013,14 @@ var init_mixed_http_methods = __esm({
77762
78013
  });
77763
78014
 
77764
78015
  // packages/analyzer/dist/rules/security/visitors/javascript/process-signaling.js
78016
+ function isOwnedProcessPid(node2) {
78017
+ if (node2.type !== "member_expression")
78018
+ return false;
78019
+ if (node2.childForFieldName("property")?.text !== "pid")
78020
+ return false;
78021
+ const obj = node2.childForFieldName("object");
78022
+ return obj?.type === "member_expression" && obj.childForFieldName("property")?.text === "process";
78023
+ }
77765
78024
  var processSignalingVisitor;
77766
78025
  var init_process_signaling = __esm({
77767
78026
  "packages/analyzer/dist/rules/security/visitors/javascript/process-signaling.js"() {
@@ -77789,6 +78048,8 @@ var init_process_signaling = __esm({
77789
78048
  const pidArg = args.namedChildren[0];
77790
78049
  if (!pidArg)
77791
78050
  return null;
78051
+ if (isOwnedProcessPid(pidArg))
78052
+ return null;
77792
78053
  if (pidArg.type !== "number") {
77793
78054
  return makeViolation(this.ruleKey, node2, filePath, "high", "Sending signals to arbitrary processes", "process.kill() called with a non-literal PID. If the PID is user-controlled, this enables process manipulation.", sourceCode, "Validate and sanitize the PID before passing it to process.kill().");
77794
78055
  }
@@ -79142,8 +79403,8 @@ var init_sql_injection2 = __esm({
79142
79403
  if (!firstArg)
79143
79404
  return null;
79144
79405
  if (firstArg.type === "string" && firstArg.text.startsWith("f")) {
79145
- const hasInterpolation2 = firstArg.namedChildren.some((c) => c.type === "interpolation");
79146
- if (hasInterpolation2) {
79406
+ const hasInterpolation = firstArg.namedChildren.some((c) => c.type === "interpolation");
79407
+ if (hasInterpolation) {
79147
79408
  const queryText = firstArg.text;
79148
79409
  if (/:[a-z_]\w*|%s|%\(\w+\)s/.test(queryText))
79149
79410
  return null;
@@ -79653,17 +79914,17 @@ var init_eval_usage2 = __esm({
79653
79914
  funcName = attr.text;
79654
79915
  const receiver = fn.childForFieldName("object");
79655
79916
  if (receiver) {
79656
- let leafName2 = null;
79917
+ let leafName3 = null;
79657
79918
  if (receiver.type === "identifier") {
79658
- leafName2 = receiver.text;
79919
+ leafName3 = receiver.text;
79659
79920
  } else if (receiver.type === "attribute") {
79660
79921
  const attrChild = receiver.childForFieldName("attribute");
79661
79922
  if (attrChild) {
79662
- leafName2 = attrChild.text.replace(/^_+/, "");
79923
+ leafName3 = attrChild.text.replace(/^_+/, "");
79663
79924
  }
79664
79925
  }
79665
- if (leafName2 && SAFE_METHOD_RECEIVERS.has(leafName2)) {
79666
- const expectedSources = SAFE_RECEIVER_IMPORT_SOURCES[leafName2];
79926
+ if (leafName3 && SAFE_METHOD_RECEIVERS.has(leafName3)) {
79927
+ const expectedSources = SAFE_RECEIVER_IMPORT_SOURCES[leafName3];
79667
79928
  if (expectedSources) {
79668
79929
  const sources = getPythonImportSources(node2);
79669
79930
  for (const src of sources) {
@@ -83635,6 +83896,12 @@ var init_exclusions = __esm({
83635
83896
  /^https?:\/\/(?!hooks\.slack\.com|[a-z0-9]+\.webhook\.office\.com)/,
83636
83897
  // Docker / container image references
83637
83898
  /^(?:docker|ghcr|quay|gcr|ecr)\.io\//,
83899
+ // PostHog public project API key (phc_…) — intentionally client-exposed,
83900
+ // embedded in frontend code to send analytics events. Its 40+ char base62
83901
+ // body collides with the generic Cohere-token shape, but it is not a
83902
+ // private credential. Private PostHog keys use the phx_/phs_ prefixes and
83903
+ // are deliberately NOT allowlisted here.
83904
+ /^phc_[A-Za-z0-9]{30,}$/,
83638
83905
  // Common config patterns that aren't secrets
83639
83906
  /^\*+$/,
83640
83907
  // All asterisks (masked values)
@@ -85450,10 +85717,33 @@ var init_secret_scanner = __esm({
85450
85717
  });
85451
85718
 
85452
85719
  // packages/analyzer/dist/rules/security/visitors/universal.js
85720
+ function hasPasswordToken(text4) {
85721
+ const lower = text4.toLowerCase();
85722
+ return lower.includes("password") || lower.includes("passwd");
85723
+ }
85724
+ function isPasswordHashingContext(node2) {
85725
+ let stmt = node2.parent;
85726
+ while (stmt && !STATEMENT_NODE_TYPES2.has(stmt.type) && !FUNCTION_NODE_TYPES3.has(stmt.type)) {
85727
+ stmt = stmt.parent;
85728
+ }
85729
+ if (stmt && STATEMENT_NODE_TYPES2.has(stmt.type) && hasPasswordToken(stmt.text))
85730
+ return true;
85731
+ let fnNode = node2.parent;
85732
+ while (fnNode && !FUNCTION_NODE_TYPES3.has(fnNode.type))
85733
+ fnNode = fnNode.parent;
85734
+ if (fnNode) {
85735
+ const nameNode = fnNode.childForFieldName("name");
85736
+ const paramsNode = fnNode.childForFieldName("parameters") ?? fnNode.childForFieldName("parameter_list");
85737
+ const signature = `${nameNode?.text ?? ""} ${paramsNode?.text ?? ""}`;
85738
+ if (hasPasswordToken(signature))
85739
+ return true;
85740
+ }
85741
+ return false;
85742
+ }
85453
85743
  function stripCommentMarkers(text4) {
85454
85744
  return text4.replace(/^\/{2,3}\s?/, "").replace(/^\/\*\s?/, "").replace(/\s?\*\/$/, "").replace(/^\s*\*\s?/gm, "").replace(/^#\s?/gm, "").trim();
85455
85745
  }
85456
- var STRING_NODE_TYPES, hardcodedSecretVisitor, IPV4_REGEX, EXCLUDED_IPS, hardcodedIpVisitor, CLEARTEXT_PROTOCOLS, LOCALHOST_PREFIXES, clearTextProtocolVisitor, BIP39_COMMON_WORDS, hardcodedBlockchainMnemonicVisitor, DB_URL_PATTERN, DB_PASSWORD_KV_PATTERNS, NON_LITERAL_PASSWORD_SEGMENT, PLACEHOLDER_PASSWORDS, hardcodedDatabasePasswordVisitor, ldapUnauthenticatedVisitor, passwordStoredPlaintextVisitor, HASH_FUNCTIONS_NEEDING_SALT, unpredictableSaltMissingVisitor, sensitiveFileVisitor, hardcodedSecretInCommentVisitor, SECURITY_UNIVERSAL_VISITORS;
85746
+ var STRING_NODE_TYPES, hardcodedSecretVisitor, IPV4_REGEX, EXCLUDED_IPS, hardcodedIpVisitor, CLEARTEXT_PROTOCOLS, LOCALHOST_PREFIXES, clearTextProtocolVisitor, BIP39_COMMON_WORDS, hardcodedBlockchainMnemonicVisitor, DB_URL_PATTERN, DB_PASSWORD_KV_PATTERNS, NON_LITERAL_PASSWORD_SEGMENT, PLACEHOLDER_PASSWORDS, hardcodedDatabasePasswordVisitor, ldapUnauthenticatedVisitor, passwordStoredPlaintextVisitor, HASH_FUNCTIONS_NEEDING_SALT, STATEMENT_NODE_TYPES2, FUNCTION_NODE_TYPES3, unpredictableSaltMissingVisitor, sensitiveFileVisitor, hardcodedSecretInCommentVisitor, SECURITY_UNIVERSAL_VISITORS;
85457
85747
  var init_universal = __esm({
85458
85748
  "packages/analyzer/dist/rules/security/visitors/universal.js"() {
85459
85749
  "use strict";
@@ -86633,6 +86923,29 @@ var init_universal = __esm({
86633
86923
  "sha256",
86634
86924
  "sha512"
86635
86925
  ]);
86926
+ STATEMENT_NODE_TYPES2 = /* @__PURE__ */ new Set([
86927
+ "expression_statement",
86928
+ "return_statement",
86929
+ "lexical_declaration",
86930
+ "variable_declaration",
86931
+ "variable_declarator",
86932
+ "local_declaration_statement",
86933
+ "assignment",
86934
+ "augmented_assignment",
86935
+ "assignment_expression",
86936
+ "augmented_assignment_expression"
86937
+ ]);
86938
+ FUNCTION_NODE_TYPES3 = /* @__PURE__ */ new Set([
86939
+ "function_declaration",
86940
+ "function_expression",
86941
+ "arrow_function",
86942
+ "generator_function_declaration",
86943
+ "method_definition",
86944
+ "function_definition",
86945
+ "method_declaration",
86946
+ "local_function_statement",
86947
+ "constructor_declaration"
86948
+ ]);
86636
86949
  unpredictableSaltMissingVisitor = {
86637
86950
  ruleKey: "security/deterministic/unpredictable-salt-missing",
86638
86951
  nodeTypes: ["call_expression", "call", "invocation_expression"],
@@ -86650,21 +86963,12 @@ var init_universal = __esm({
86650
86963
  }
86651
86964
  if (!HASH_FUNCTIONS_NEEDING_SALT.has(methodName3))
86652
86965
  return null;
86653
- let parent = node2.parent;
86654
- while (parent) {
86655
- const parentText = parent.text.toLowerCase();
86656
- if (parentText.includes("password") || parentText.includes("passwd")) {
86657
- const args = node2.childForFieldName("arguments");
86658
- if (args && args.namedChildren.length < 2) {
86659
- return makeViolation(this.ruleKey, node2, filePath, "high", "Missing salt in hash", `${methodName3}() called without a salt in a password context. This allows rainbow table attacks.`, sourceCode, "Use a password hashing function like bcrypt, argon2, or PBKDF2 which handle salting automatically.");
86660
- }
86661
- return null;
86662
- }
86663
- if (parent.type === "function_declaration" || parent.type === "function_definition" || parent.type === "method_definition" || parent.type === "program" || parent.type === "method_declaration" || parent.type === "compilation_unit")
86664
- break;
86665
- parent = parent.parent;
86666
- }
86667
- return null;
86966
+ const args = node2.childForFieldName("arguments");
86967
+ if (!args || args.namedChildren.length >= 2)
86968
+ return null;
86969
+ if (!isPasswordHashingContext(node2))
86970
+ return null;
86971
+ return makeViolation(this.ruleKey, node2, filePath, "high", "Missing salt in hash", `${methodName3}() called without a salt in a password context. This allows rainbow table attacks.`, sourceCode, "Use a password hashing function like bcrypt, argon2, or PBKDF2 which handle salting automatically.");
86668
86972
  }
86669
86973
  };
86670
86974
  sensitiveFileVisitor = {
@@ -86757,7 +87061,7 @@ function isLiteralNode(n) {
86757
87061
  }
86758
87062
  return false;
86759
87063
  }
86760
- var JS_LANGUAGES, TS_LANGUAGES, COMPARISON_OPERATORS, CONSTANT_LITERALS, TERMINAL_TYPES, VALID_TYPEOF_VALUES, LITERAL_TYPES, ARRAY_METHODS_REQUIRING_RETURN, PRIMITIVE_TYPES, PURE_ARRAY_METHODS, KNOWN_ARG_ORDERS, VOID_RETURNING_METHODS, VOID_RETURNING_GLOBALS, NON_CONSTRUCTORS, PROTOTYPE_BUILTINS, NON_CALLABLE_GLOBALS, READ_ONLY_GLOBALS, RESTRICTED_NAMES, THROWABLE_TYPES;
87064
+ var JS_LANGUAGES, TS_LANGUAGES, COMPARISON_OPERATORS, CONSTANT_LITERALS, TERMINAL_TYPES, VALID_TYPEOF_VALUES, LITERAL_TYPES, ARRAY_METHODS_REQUIRING_RETURN, PRIMITIVE_TYPES, PURE_ARRAY_METHODS, SOCKET_IO_RECEIVERS, KNOWN_ARG_ORDERS, VOID_RETURNING_METHODS, VOID_RETURNING_GLOBALS, NON_CONSTRUCTORS, PROTOTYPE_BUILTINS, NON_CALLABLE_GLOBALS, READ_ONLY_GLOBALS, RESTRICTED_NAMES, THROWABLE_TYPES;
86761
87065
  var init_helpers = __esm({
86762
87066
  "packages/analyzer/dist/rules/bugs/visitors/javascript/_helpers.js"() {
86763
87067
  "use strict";
@@ -86815,6 +87119,7 @@ var init_helpers = __esm({
86815
87119
  "reduce",
86816
87120
  "reduceRight"
86817
87121
  ]);
87122
+ SOCKET_IO_RECEIVERS = /* @__PURE__ */ new Set(["socket", "sockets"]);
86818
87123
  KNOWN_ARG_ORDERS = [
86819
87124
  { fn: "startsWith", params: [["prefix", "str", "string", "start", "needle", "search"]] },
86820
87125
  { fn: "endsWith", params: [["suffix", "str", "string", "end", "needle", "search"]] },
@@ -87451,6 +87756,15 @@ var init_loss_of_precision = __esm({
87451
87756
  const num = Number(text4);
87452
87757
  if (!Number.isFinite(num))
87453
87758
  return null;
87759
+ const parent = node2.parent;
87760
+ if (parent?.type === "arguments") {
87761
+ const call = parent.parent;
87762
+ if (call?.type === "call_expression") {
87763
+ const fn = call.childForFieldName("function");
87764
+ if (fn?.type === "identifier" && fn.text === "BigInt")
87765
+ return null;
87766
+ }
87767
+ }
87454
87768
  if (!Number.isSafeInteger(num)) {
87455
87769
  return makeViolation(this.ruleKey, node2, filePath, "high", "Loss of precision", `\`${text4}\` exceeds Number.MAX_SAFE_INTEGER and will lose precision at runtime.`, sourceCode, `Use BigInt (\`${text4}n\`) or restructure to avoid large integer literals.`);
87456
87770
  }
@@ -89722,6 +90036,17 @@ var init_useless_increment = __esm({
89722
90036
  });
89723
90037
 
89724
90038
  // packages/analyzer/dist/rules/bugs/visitors/javascript/ignored-return-value.js
90039
+ function receiverBaseName(obj) {
90040
+ if (!obj)
90041
+ return null;
90042
+ if (obj.type === "identifier")
90043
+ return obj.text.toLowerCase();
90044
+ if (obj.type === "member_expression") {
90045
+ const prop = obj.childForFieldName("property");
90046
+ return prop ? prop.text.toLowerCase() : null;
90047
+ }
90048
+ return null;
90049
+ }
89725
90050
  var ignoredReturnValueVisitor;
89726
90051
  var init_ignored_return_value = __esm({
89727
90052
  "packages/analyzer/dist/rules/bugs/visitors/javascript/ignored-return-value.js"() {
@@ -89742,6 +90067,11 @@ var init_ignored_return_value = __esm({
89742
90067
  const prop = fn.childForFieldName("property");
89743
90068
  if (!prop || !PURE_ARRAY_METHODS.has(prop.text))
89744
90069
  return null;
90070
+ if (prop.text === "join") {
90071
+ const base = receiverBaseName(fn.childForFieldName("object"));
90072
+ if (base && SOCKET_IO_RECEIVERS.has(base))
90073
+ return null;
90074
+ }
89745
90075
  return makeViolation(this.ruleKey, expr, filePath, "high", "Ignored return value", `The return value of \`.${prop.text}()\` is ignored \u2014 this method does not mutate the array in place; the result must be used.`, sourceCode, `Assign the result: \`const result = arr.${prop.text}(...)\`.`);
89746
90076
  }
89747
90077
  };
@@ -90403,7 +90733,12 @@ var init_global_this_usage = __esm({
90403
90733
  let current = node2.parent;
90404
90734
  while (current) {
90405
90735
  const t = current.type;
90406
- if (t === "function_declaration" || t === "function" || t === "method_definition" || t === "class_declaration" || t === "class") {
90736
+ if (t === "function_declaration" || t === "function" || // Modern tree-sitter-javascript names a `function () {}` expression
90737
+ // `function_expression` (only the legacy grammar used bare `function`).
90738
+ // Without it, `this` inside any function-expression callback / IIFE /
90739
+ // prototype-assigned method walks up to program scope and is wrongly
90740
+ // reported as a global reference.
90741
+ t === "function_expression" || t === "generator_function" || t === "generator_function_declaration" || t === "method_definition" || t === "class_declaration" || t === "class") {
90407
90742
  return null;
90408
90743
  }
90409
90744
  if (t === "arrow_function") {
@@ -93069,6 +93404,9 @@ var init_missing_await = __esm({
93069
93404
  return null;
93070
93405
  if (!isInsideAsyncFunction(node2))
93071
93406
  return null;
93407
+ const declaredName = node2.childForFieldName("name");
93408
+ if (declaredName?.type === "identifier" && /Promise$/.test(declaredName.text))
93409
+ return null;
93072
93410
  const isPromise = typeQuery.isPromiseLike(filePath, value.startPosition.row, value.startPosition.column, value.endPosition.row, value.endPosition.column);
93073
93411
  if (!isPromise)
93074
93412
  return null;
@@ -93645,25 +93983,26 @@ var init_unbound_method = __esm({
93645
93983
  let classBody = node2.parent;
93646
93984
  while (classBody && classBody.type !== "class_body")
93647
93985
  classBody = classBody.parent;
93648
- if (classBody) {
93649
- for (const member of classBody.namedChildren) {
93650
- if (member.type === "public_field_definition" || member.type === "field_definition") {
93651
- const nameNode = member.children.find((c) => c.type === "property_identifier" || c.type === "private_property_identifier");
93652
- if (nameNode?.text !== methodName3)
93653
- continue;
93654
- const value = member.childForFieldName("value");
93655
- if (value?.type === "arrow_function" || value?.type === "function_expression")
93656
- return null;
93657
- if (value)
93658
- return null;
93659
- const typeAnnot = member.namedChildren.find((c) => c.type === "type_annotation");
93660
- const typeChild = typeAnnot?.namedChildren[0];
93661
- if (typeChild && typeChild.type !== "function_type" && typeChild.type !== "constructor_type") {
93662
- return null;
93663
- }
93664
- }
93986
+ if (!classBody)
93987
+ return null;
93988
+ let resolvesToMethod = false;
93989
+ for (const member of classBody.namedChildren) {
93990
+ if (member.type === "method_definition") {
93991
+ const nameNode = member.childForFieldName("name");
93992
+ if (nameNode?.text !== methodName3)
93993
+ continue;
93994
+ const isAccessor = member.children.some((c) => (c.text === "get" || c.text === "set") && c.type !== "property_identifier");
93995
+ resolvesToMethod = !isAccessor;
93996
+ break;
93997
+ }
93998
+ if (member.type === "public_field_definition" || member.type === "field_definition") {
93999
+ const nameNode = member.children.find((c) => c.type === "property_identifier" || c.type === "private_property_identifier");
94000
+ if (nameNode?.text === methodName3)
94001
+ return null;
93665
94002
  }
93666
94003
  }
94004
+ if (!resolvesToMethod)
94005
+ return null;
93667
94006
  return makeViolation(this.ruleKey, node2, filePath, "high", "Unbound method reference", `\`this.${methodName3}\` is used as a value without binding \u2014 \`this\` will be lost when called as a callback.`, sourceCode, "Use `this.method.bind(this)`, an arrow wrapper `() => this.method()`, or declare as an arrow function property.");
93668
94007
  }
93669
94008
  };
@@ -93766,7 +94105,7 @@ var init_missing_error_boundary = __esm({
93766
94105
  visit(node2, filePath, sourceCode) {
93767
94106
  if (!sourceCode.includes("React") && !sourceCode.includes("react") && !sourceCode.includes("jsx"))
93768
94107
  return null;
93769
- const hasAsyncData = /(?<![.\w])useQuery\b/.test(sourceCode) || /(?<![.\w])useSWR\b/.test(sourceCode) || /\buseSuspenseQuery\b/.test(sourceCode) || /\buseSuspenseInfiniteQuery\b/.test(sourceCode) || /\bSuspense\b/.test(sourceCode);
94108
+ const hasAsyncData = /(?<![.\w])useQuery\b/.test(sourceCode) || /(?<![.\w])useSWR\b/.test(sourceCode) || /\buseSuspenseQuery\b/.test(sourceCode) || /\buseSuspenseInfiniteQuery\b/.test(sourceCode);
93770
94109
  if (!hasAsyncData)
93771
94110
  return null;
93772
94111
  if (/\bErrorBoundary\b/.test(sourceCode))
@@ -106591,6 +106930,9 @@ var init_star_import = __esm({
106591
106930
  if (sourceText === "zod" || sourceText.startsWith("@react-email/") || sourceText.startsWith("fumadocs-ui/") || sourceText.startsWith("fumadocs-core/")) {
106592
106931
  return null;
106593
106932
  }
106933
+ if (sourceText === "semver" || sourceText.startsWith("@sentry/")) {
106934
+ return null;
106935
+ }
106594
106936
  if (sourceText.startsWith("./") || sourceText.startsWith("../"))
106595
106937
  return null;
106596
106938
  const nsImport = node2.namedChildren.find((c) => c.type === "import_clause")?.namedChildren.find((cc) => cc.type === "namespace_import");
@@ -108159,14 +108501,20 @@ var init_expression_complexity = __esm({
108159
108501
  if (!expr)
108160
108502
  return null;
108161
108503
  let target = expr;
108162
- while (target.type === "parenthesized_expression" && target.namedChildren[0]) {
108504
+ while ((target.type === "parenthesized_expression" || target.type === "as_expression" || target.type === "satisfies_expression" || target.type === "non_null_expression") && target.namedChildren[0]) {
108163
108505
  target = target.namedChildren[0];
108164
108506
  }
108507
+ if (target.type === "object" || target.type === "array")
108508
+ return null;
108165
108509
  if (target.type === "arrow_function" || target.type === "function_expression" || target.type === "function_declaration" || target.type === "generator_function" || target.type === "generator_function_declaration")
108166
108510
  return null;
108167
108511
  if (target.type === "jsx_element" || target.type === "jsx_fragment" || target.type === "jsx_self_closing_element")
108168
108512
  return null;
108169
108513
  let operatorCount = 0;
108514
+ let hasLogical = false;
108515
+ let hasArithmetic = false;
108516
+ const LOGICAL_OPS = /* @__PURE__ */ new Set(["&&", "||", "??"]);
108517
+ const ARITHMETIC_OPS3 = /* @__PURE__ */ new Set(["+", "-", "*", "/", "%", "**", "&", "|", "^", "<<", ">>", ">>>"]);
108170
108518
  const BINARY_TYPES = /* @__PURE__ */ new Set(["binary_expression", "logical_expression"]);
108171
108519
  const FUNCTION_BOUNDARY_TYPES3 = /* @__PURE__ */ new Set([
108172
108520
  "function_declaration",
@@ -108179,6 +108527,13 @@ var init_expression_complexity = __esm({
108179
108527
  function countOps(n) {
108180
108528
  if (BINARY_TYPES.has(n.type)) {
108181
108529
  operatorCount++;
108530
+ const op = n.childForFieldName("operator")?.text;
108531
+ if (op) {
108532
+ if (LOGICAL_OPS.has(op))
108533
+ hasLogical = true;
108534
+ else if (ARITHMETIC_OPS3.has(op))
108535
+ hasArithmetic = true;
108536
+ }
108182
108537
  }
108183
108538
  for (let i = 0; i < n.childCount; i++) {
108184
108539
  const child = n.child(i);
@@ -108191,6 +108546,8 @@ var init_expression_complexity = __esm({
108191
108546
  }
108192
108547
  countOps(expr);
108193
108548
  if (operatorCount > 5) {
108549
+ if (hasLogical && !hasArithmetic)
108550
+ return null;
108194
108551
  return makeViolation(this.ruleKey, node2, filePath, "medium", "Complex expression", `Expression has ${operatorCount} binary/logical operators (max 5). Break it into named variables for readability.`, sourceCode, "Split the expression into smaller, named intermediate variables.");
108195
108552
  }
108196
108553
  return null;
@@ -109015,12 +109372,73 @@ var init_strict_equality = __esm({
109015
109372
  }
109016
109373
  });
109017
109374
 
109018
- // packages/analyzer/dist/rules/code-quality/visitors/javascript/commented-out-code.js
109019
- var commentedOutCodeVisitor;
109375
+ // packages/analyzer/dist/rules/code-quality/visitors/_shared/commented-out-code.js
109376
+ function stripLineMarker(line) {
109377
+ return line.replace(/^\s*(?:\/\/+|#+)\s?/, "").replace(/^\s*\*\/?\s?/, "").replace(/\*\/\s*$/, "").trim();
109378
+ }
109379
+ function looksLikeProse(line) {
109380
+ const words = line.match(/[A-Za-z][A-Za-z'-]+/g) ?? [];
109381
+ const codePunct = (line.match(/[;{}()=<>[\]]/g) ?? []).length;
109382
+ return words.length >= 4 && codePunct <= 1;
109383
+ }
109384
+ function looksLikeCode(line, opts) {
109385
+ if (/[;{}]\s*$/.test(line))
109386
+ return true;
109387
+ if (opts.colonTerminates && /:\s*$/.test(line))
109388
+ return true;
109389
+ if (/=>/.test(line))
109390
+ return true;
109391
+ if (/^[\w.]+\s*\([^)]*\)\s*;?\s*$/.test(line))
109392
+ return true;
109393
+ if (/^[\w.[\]]+\s*=\s*[^=].*$/.test(line) && !/==/.test(line))
109394
+ return true;
109395
+ return false;
109396
+ }
109397
+ function isCommentedOutCode(inner, opts) {
109398
+ let code = 0;
109399
+ let prose = 0;
109400
+ for (const raw of inner.split("\n")) {
109401
+ const line = stripLineMarker(raw);
109402
+ if (line.length === 0)
109403
+ continue;
109404
+ if (looksLikeProse(line))
109405
+ prose++;
109406
+ else if (looksLikeCode(line, opts))
109407
+ code++;
109408
+ }
109409
+ return code > 0 && code >= prose;
109410
+ }
109411
+ function lineCommentRunInner(node2, opts) {
109412
+ const isRunLine = (n) => n != null && n.type === "comment" && opts.isLineComment(n.text);
109413
+ if (!isRunLine(node2))
109414
+ return null;
109415
+ const prev = node2.previousSibling;
109416
+ if (isRunLine(prev) && prev.startPosition.row === node2.startPosition.row - 1)
109417
+ return null;
109418
+ const lines = [opts.strip(node2.text)];
109419
+ let cur = node2.nextSibling;
109420
+ let lastRow = node2.startPosition.row;
109421
+ while (isRunLine(cur) && cur.startPosition.row === lastRow + 1) {
109422
+ lines.push(opts.strip(cur.text));
109423
+ lastRow = cur.startPosition.row;
109424
+ cur = cur.nextSibling;
109425
+ }
109426
+ return lines.join("\n");
109427
+ }
109020
109428
  var init_commented_out_code = __esm({
109429
+ "packages/analyzer/dist/rules/code-quality/visitors/_shared/commented-out-code.js"() {
109430
+ "use strict";
109431
+ }
109432
+ });
109433
+
109434
+ // packages/analyzer/dist/rules/code-quality/visitors/javascript/commented-out-code.js
109435
+ var isLineComment, commentedOutCodeVisitor;
109436
+ var init_commented_out_code2 = __esm({
109021
109437
  "packages/analyzer/dist/rules/code-quality/visitors/javascript/commented-out-code.js"() {
109022
109438
  "use strict";
109023
109439
  init_types3();
109440
+ init_commented_out_code();
109441
+ isLineComment = (t) => t.startsWith("//");
109024
109442
  commentedOutCodeVisitor = {
109025
109443
  ruleKey: "code-quality/deterministic/commented-out-code",
109026
109444
  languages: ["typescript", "tsx", "javascript"],
@@ -109029,21 +109447,19 @@ var init_commented_out_code = __esm({
109029
109447
  const text4 = node2.text;
109030
109448
  if (text4.startsWith("/**"))
109031
109449
  return null;
109032
- let inner = text4;
109033
- if (inner.startsWith("//"))
109034
- inner = inner.slice(2).trim();
109035
- else if (inner.startsWith("/*"))
109036
- inner = inner.slice(2, -2).trim();
109037
- if (inner.length < 10)
109038
- return null;
109039
- const codePatterns = [
109040
- /^\s*(const|let|var|function|return|if|for|while|import|export|class|throw|try|catch)\s/,
109041
- /[;{}]\s*$/,
109042
- /=>/,
109043
- /\w+\(.*\)\s*[;{]?\s*$/
109044
- ];
109045
- const matchCount = codePatterns.filter((p) => p.test(inner)).length;
109046
- if (matchCount >= 2) {
109450
+ let inner;
109451
+ if (isLineComment(text4)) {
109452
+ inner = lineCommentRunInner(node2, { isLineComment, strip: (t) => t.replace(/^\/\/+/, "") });
109453
+ if (inner === null)
109454
+ return null;
109455
+ } else if (text4.startsWith("/*")) {
109456
+ inner = text4.slice(2, -2);
109457
+ } else {
109458
+ return null;
109459
+ }
109460
+ if (inner.trim().length < 10)
109461
+ return null;
109462
+ if (isCommentedOutCode(inner, {})) {
109047
109463
  return makeViolation(this.ruleKey, node2, filePath, "low", "Commented-out code", "This comment appears to contain commented-out code. Remove it or track it in version control.", sourceCode, "Delete the commented-out code. If needed, it can be recovered from version control.");
109048
109464
  }
109049
109465
  return null;
@@ -109824,6 +110240,11 @@ var init_for_in_without_filter = __esm({
109824
110240
  return true;
109825
110241
  }
109826
110242
  }
110243
+ if (n.type === "member_expression") {
110244
+ const prop = n.childForFieldName("property");
110245
+ if (prop?.text === "hasOwnProperty" || prop?.text === "hasOwn")
110246
+ return true;
110247
+ }
109827
110248
  if (n.type === "string" && (n.text.includes("hasOwnProperty") || n.text.includes("hasOwn")))
109828
110249
  return true;
109829
110250
  for (let i = 0; i < n.childCount; i++) {
@@ -111191,6 +111612,10 @@ var init_selector_parameter = __esm({
111191
111612
  languages: ["typescript", "tsx", "javascript"],
111192
111613
  nodeTypes: JS_FUNCTION_TYPES,
111193
111614
  visit(node2, filePath, sourceCode) {
111615
+ const fnParent = node2.parent;
111616
+ if ((node2.type === "arrow_function" || node2.type === "function_expression") && (fnParent?.type === "arguments" || fnParent?.type === "jsx_expression")) {
111617
+ return null;
111618
+ }
111194
111619
  const params = node2.childForFieldName("parameters");
111195
111620
  if (!params)
111196
111621
  return null;
@@ -113273,6 +113698,15 @@ var init_meaningless_void_operator = __esm({
113273
113698
  });
113274
113699
 
113275
113700
  // packages/analyzer/dist/rules/code-quality/visitors/javascript/test-with-hardcoded-timeout.js
113701
+ function isTestFile(filePath) {
113702
+ const norm = filePath.replace(/\\/g, "/");
113703
+ const base = norm.slice(norm.lastIndexOf("/") + 1);
113704
+ if (/\.(test|spec)\.[cm]?[jt]sx?$/i.test(base))
113705
+ return true;
113706
+ if (/(^|\/)__tests?__\//.test(norm))
113707
+ return true;
113708
+ return false;
113709
+ }
113276
113710
  var testWithHardcodedTimeoutVisitor;
113277
113711
  var init_test_with_hardcoded_timeout = __esm({
113278
113712
  "packages/analyzer/dist/rules/code-quality/visitors/javascript/test-with-hardcoded-timeout.js"() {
@@ -113283,7 +113717,7 @@ var init_test_with_hardcoded_timeout = __esm({
113283
113717
  languages: ["typescript", "tsx", "javascript"],
113284
113718
  nodeTypes: ["call_expression"],
113285
113719
  visit(node2, filePath, sourceCode) {
113286
- if (!filePath.includes("test") && !filePath.includes("spec") && !filePath.includes(".test.") && !filePath.includes(".spec."))
113720
+ if (!isTestFile(filePath))
113287
113721
  return null;
113288
113722
  const fn = node2.childForFieldName("function");
113289
113723
  if (!fn)
@@ -114643,8 +115077,8 @@ var init_test_empty_file = __esm({
114643
115077
  nodeTypes: ["program"],
114644
115078
  visit(node2, filePath, sourceCode) {
114645
115079
  const lowerPath = filePath.toLowerCase();
114646
- const isTestFile5 = lowerPath.includes(".test.") || lowerPath.includes(".spec.") || lowerPath.includes("__tests__");
114647
- if (!isTestFile5)
115080
+ const isTestFile6 = lowerPath.includes(".test.") || lowerPath.includes(".spec.") || lowerPath.includes("__tests__");
115081
+ if (!isTestFile6)
114648
115082
  return null;
114649
115083
  if (hasTestFunction(node2))
114650
115084
  return null;
@@ -118350,7 +118784,7 @@ var init_javascript5 = __esm({
118350
118784
  init_useless_computed_key();
118351
118785
  init_useless_concat();
118352
118786
  init_strict_equality();
118353
- init_commented_out_code();
118787
+ init_commented_out_code2();
118354
118788
  init_inverted_boolean();
118355
118789
  init_prefer_single_boolean_return();
118356
118790
  init_prefer_immediate_return();
@@ -119517,30 +119951,27 @@ var init_unused_variable2 = __esm({
119517
119951
  });
119518
119952
 
119519
119953
  // packages/analyzer/dist/rules/code-quality/visitors/python/commented-out-code.js
119520
- var pythonCommentedOutCodeVisitor;
119521
- var init_commented_out_code2 = __esm({
119954
+ var isLineComment2, pythonCommentedOutCodeVisitor;
119955
+ var init_commented_out_code3 = __esm({
119522
119956
  "packages/analyzer/dist/rules/code-quality/visitors/python/commented-out-code.js"() {
119523
119957
  "use strict";
119524
119958
  init_types3();
119959
+ init_commented_out_code();
119960
+ isLineComment2 = (t) => t.startsWith("#");
119525
119961
  pythonCommentedOutCodeVisitor = {
119526
119962
  ruleKey: "code-quality/deterministic/commented-out-code",
119527
119963
  languages: ["python"],
119528
119964
  nodeTypes: ["comment"],
119529
119965
  visit(node2, filePath, sourceCode) {
119530
119966
  const text4 = node2.text;
119531
- if (!text4.startsWith("#"))
119967
+ if (!isLineComment2(text4))
119532
119968
  return null;
119533
- const inner = text4.slice(1).trim();
119534
- if (inner.length < 10)
119969
+ const inner = lineCommentRunInner(node2, { isLineComment: isLineComment2, strip: (t) => t.replace(/^#+/, "") });
119970
+ if (inner === null)
119535
119971
  return null;
119536
- const codePatterns = [
119537
- /\b(def|class|import|from|return|if|for|while|try|except|with|raise|print)\b/,
119538
- /[;:]\s*$/,
119539
- /\w+\(.*\)/,
119540
- /=\s*\w/
119541
- ];
119542
- const matchCount = codePatterns.filter((p) => p.test(inner)).length;
119543
- if (matchCount >= 2) {
119972
+ if (inner.trim().length < 10)
119973
+ return null;
119974
+ if (isCommentedOutCode(inner, { colonTerminates: true })) {
119544
119975
  return makeViolation(this.ruleKey, node2, filePath, "low", "Commented-out code", "This comment appears to contain commented-out code. Remove it or track it in version control.", sourceCode, "Delete the commented-out code. If needed, it can be recovered from version control.");
119545
119976
  }
119546
119977
  return null;
@@ -128533,8 +128964,8 @@ var init_test_not_discoverable = __esm({
128533
128964
  return null;
128534
128965
  const className = classNameNode.text;
128535
128966
  const isTestClass = className.startsWith("Test") || className.endsWith("Test") || className.endsWith("Tests");
128536
- const isTestFile5 = isPythonTestFile(filePath);
128537
- if (!isTestClass && !isTestFile5)
128967
+ const isTestFile6 = isPythonTestFile(filePath);
128968
+ if (!isTestClass && !isTestFile6)
128538
128969
  return null;
128539
128970
  const body = node2.childForFieldName("body");
128540
128971
  if (!body)
@@ -130303,7 +130734,7 @@ var init_python7 = __esm({
130303
130734
  init_no_debugger2();
130304
130735
  init_require_await2();
130305
130736
  init_unused_variable2();
130306
- init_commented_out_code2();
130737
+ init_commented_out_code3();
130307
130738
  init_abstract_class_without_abstract_method();
130308
130739
  init_and_or_ternary();
130309
130740
  init_assert_in_production();
@@ -131477,7 +131908,18 @@ function withLanguageSupport(rules, visitors) {
131477
131908
  continue;
131478
131909
  }
131479
131910
  if (rule.engine === "roslyn-host" || rule.engine === "roslyn-workspace") {
131480
- support[language] = language === "csharp" ? { status: "supported" } : { status: "not-applicable", reason: "C# semantic rule (Roslyn host); no equivalent in this language" };
131911
+ if (language === "csharp") {
131912
+ support[language] = { status: "supported" };
131913
+ continue;
131914
+ }
131915
+ const hasExplicitForLang = explicitFamilies.get(rule.key)?.has(language) ?? false;
131916
+ const universalExcludesLang2 = universalExcludedFamilies.get(rule.key)?.has(language) ?? false;
131917
+ const hasUniversalForLang = universalRuleKeys.has(rule.key) && UNIVERSAL_VISITOR_FAMILIES.includes(language) && !universalExcludesLang2;
131918
+ if (hasExplicitForLang || hasUniversalForLang) {
131919
+ support[language] = { status: "supported" };
131920
+ continue;
131921
+ }
131922
+ support[language] = { status: "not-applicable", reason: "C# semantic rule (Roslyn host); no equivalent in this language" };
131481
131923
  continue;
131482
131924
  }
131483
131925
  const hasExplicit = explicitFamilies.get(rule.key)?.has(language) ?? false;
@@ -140236,6 +140678,12 @@ function walkCSharp(node2, visit) {
140236
140678
  walkCSharp(child, visit);
140237
140679
  }
140238
140680
  }
140681
+ function isCSharpTestPath(filePath) {
140682
+ return filePath.split(/[\\/]/).some((segment) => {
140683
+ const s = segment.toLowerCase();
140684
+ return /\.tests?$/.test(s) || s.endsWith(".testbase") || s.endsWith("testapp");
140685
+ });
140686
+ }
140239
140687
  var FUNCTION_BOUNDARY_TYPES;
140240
140688
  var init_csharp_helpers = __esm({
140241
140689
  "packages/analyzer/dist/rules/_shared/csharp-helpers.js"() {
@@ -141650,7 +142098,7 @@ function isTypeConstantAccess(node2) {
141650
142098
  const receiver = lastSegment(node2.childForFieldName("expression")?.text ?? "");
141651
142099
  return /^[A-Z]/.test(receiver);
141652
142100
  }
141653
- function leafName(node2) {
142101
+ function leafName2(node2) {
141654
142102
  if (node2.type === "identifier")
141655
142103
  return node2.text;
141656
142104
  if (node2.type === "member_access_expression")
@@ -141686,8 +142134,8 @@ var init_timing_attack_comparison2 = __esm({
141686
142134
  return null;
141687
142135
  if (isTypeConstantAccess(left) || isTypeConstantAccess(right))
141688
142136
  return null;
141689
- const leftLeaf = leafName(left);
141690
- const rightLeaf = leafName(right);
142137
+ const leftLeaf = leafName2(left);
142138
+ const rightLeaf = leafName2(right);
141691
142139
  if (leftLeaf === "Length" || rightLeaf === "Length")
141692
142140
  return null;
141693
142141
  if (ENUM_CONSTANT_PATTERN2.test(leftLeaf) || ENUM_CONSTANT_PATTERN2.test(rightLeaf))
@@ -141706,7 +142154,7 @@ var init_timing_attack_comparison2 = __esm({
141706
142154
  });
141707
142155
 
141708
142156
  // packages/analyzer/dist/rules/security/visitors/csharp/insecure-random.js
141709
- function isTestFile(filePath) {
142157
+ function isTestFile2(filePath) {
141710
142158
  const fileName = filePath.replace(/^.*[\\/]/, "");
141711
142159
  return /Tests?\.cs$/i.test(fileName) || /(?:TestSetup|TestFixture|Fixtures?|FunctionalTests|IntegrationTests|UnitTests|TestHelpers?)/.test(fileName);
141712
142160
  }
@@ -141748,7 +142196,7 @@ var init_insecure_random3 = __esm({
141748
142196
  languages: ["csharp"],
141749
142197
  nodeTypes: ["object_creation_expression", "member_access_expression"],
141750
142198
  visit(node2, filePath, sourceCode) {
141751
- if (isTestFile(filePath))
142199
+ if (isTestFile2(filePath))
141752
142200
  return null;
141753
142201
  if (node2.type === "object_creation_expression") {
141754
142202
  if (getCreatedTypeName(node2) !== "Random")
@@ -144219,11 +144667,14 @@ var init_empty_catch3 = __esm({
144219
144667
  const stmt = tryStatements[0];
144220
144668
  if (/\b(Parse|TryParse|Deserialize)\s*[<(]/.test(stmt.text))
144221
144669
  return null;
144222
- const expr = stmt.namedChildren[0];
144223
- if (expr?.type === "invocation_expression" && BEST_EFFORT_METHODS.has(getCSharpMethodName(expr))) {
144670
+ let expr = stmt.namedChildren[0];
144671
+ if (expr?.type === "await_expression")
144672
+ expr = expr.namedChildren[0];
144673
+ const isBestEffort = (name) => BEST_EFFORT_METHODS.has(name.replace(/Async$/, ""));
144674
+ if (expr?.type === "invocation_expression" && isBestEffort(getCSharpMethodName(expr))) {
144224
144675
  return null;
144225
144676
  }
144226
- if (expr?.type === "conditional_access_expression" && BEST_EFFORT_METHODS.has((expr.text.match(/\.(\w+)\s*\([^()]*\)\s*$/) ?? [])[1] ?? "")) {
144677
+ if (expr?.type === "conditional_access_expression" && isBestEffort((expr.text.match(/\.(\w+)\s*\([^()]*\)\s*$/) ?? [])[1] ?? "")) {
144227
144678
  return null;
144228
144679
  }
144229
144680
  }
@@ -144287,6 +144738,8 @@ var init_self_assignment3 = __esm({
144287
144738
  languages: ["csharp"],
144288
144739
  nodeTypes: ["assignment_expression"],
144289
144740
  visit(node2, filePath, sourceCode) {
144741
+ if (node2.parent?.type === "initializer_expression")
144742
+ return null;
144290
144743
  const left = node2.childForFieldName("left");
144291
144744
  const right = node2.childForFieldName("right");
144292
144745
  const operator = node2.childForFieldName("operator");
@@ -145294,7 +145747,7 @@ var init_infinite_recursion2 = __esm({
145294
145747
  return null;
145295
145748
  if (node2.type === "property_declaration") {
145296
145749
  const arrow = node2.childForFieldName("value");
145297
- const arrowExpr = arrow?.namedChildren[0];
145750
+ const arrowExpr = arrow?.type === "arrow_expression_clause" ? arrow.namedChildren[0] : null;
145298
145751
  if (arrowExpr?.type === "identifier" && arrowExpr.text === name) {
145299
145752
  return makeViolation(this.ruleKey, node2, filePath, "critical", "Infinite recursion", `Property \`${name}\` returns itself \u2014 reading it recurses until StackOverflowException.`, sourceCode, "Return the backing field (or use an auto-property) instead of the property itself.");
145300
145753
  }
@@ -147359,8 +147812,11 @@ var init_useless_finally2 = __esm({
147359
147812
  });
147360
147813
 
147361
147814
  // packages/analyzer/dist/rules/bugs/visitors/csharp/fstring-missing-placeholders.js
147362
- function hasInterpolation(node2) {
147363
- return node2.namedChildren.some((c) => c?.type === "interpolation");
147815
+ function hasRealHole(node2) {
147816
+ if (node2.namedChildren.some((c) => c?.type === "interpolation"))
147817
+ return true;
147818
+ const stripped = node2.text.replace(/\{\{/g, "").replace(/\}\}/g, "");
147819
+ return stripped.includes("{");
147364
147820
  }
147365
147821
  var csharpFstringMissingPlaceholdersVisitor;
147366
147822
  var init_fstring_missing_placeholders2 = __esm({
@@ -147372,7 +147828,7 @@ var init_fstring_missing_placeholders2 = __esm({
147372
147828
  languages: ["csharp"],
147373
147829
  nodeTypes: ["interpolated_string_expression"],
147374
147830
  visit(node2, filePath, sourceCode) {
147375
- if (hasInterpolation(node2))
147831
+ if (hasRealHole(node2))
147376
147832
  return null;
147377
147833
  let top = node2;
147378
147834
  while (top.parent?.type === "binary_expression" && top.parent.childForFieldName("operator")?.text === "+") {
@@ -147383,7 +147839,7 @@ var init_fstring_missing_placeholders2 = __esm({
147383
147839
  const walk20 = (n) => {
147384
147840
  if (siblingHasHoles)
147385
147841
  return;
147386
- if (n.type === "interpolated_string_expression" && n.id !== node2.id && hasInterpolation(n)) {
147842
+ if (n.type === "interpolated_string_expression" && n.id !== node2.id && hasRealHole(n)) {
147387
147843
  siblingHasHoles = true;
147388
147844
  return;
147389
147845
  }
@@ -148863,6 +149319,32 @@ var init_chained_orderby_loses_ordering = __esm({
148863
149319
  });
148864
149320
 
148865
149321
  // packages/analyzer/dist/rules/bugs/visitors/csharp/check-against-value-being-assigned.js
149322
+ function enclosingTypeDecl(node2) {
149323
+ let p = node2.parent;
149324
+ while (p) {
149325
+ if (p.type === "class_declaration" || p.type === "struct_declaration" || p.type === "record_declaration") {
149326
+ return p;
149327
+ }
149328
+ p = p.parent;
149329
+ }
149330
+ return null;
149331
+ }
149332
+ function selfMemberName(target) {
149333
+ if (target.type === "identifier")
149334
+ return target.text;
149335
+ if (target.type === "member_access_expression") {
149336
+ const obj = target.childForFieldName("expression");
149337
+ if (obj?.type === "this")
149338
+ return target.childForFieldName("name")?.text ?? null;
149339
+ }
149340
+ return null;
149341
+ }
149342
+ function enclosingTypeHasProperty(typeDecl, name) {
149343
+ const body = typeDecl.childForFieldName("body") ?? typeDecl.namedChildren.find((c) => c?.type === "declaration_list");
149344
+ if (!body)
149345
+ return false;
149346
+ return body.namedChildren.some((m) => m?.type === "property_declaration" && m.childForFieldName("name")?.text === name);
149347
+ }
148866
149348
  function singleStatement(consequence) {
148867
149349
  if (consequence.type === "block") {
148868
149350
  const stmts = consequence.namedChildren.filter((c) => c?.type !== "comment");
@@ -148910,6 +149392,12 @@ var init_check_against_value_being_assigned = __esm({
148910
149392
  const sameBA = condLeft.text === value.text && condRight.text === target.text;
148911
149393
  if (!sameAB && !sameBA)
148912
149394
  return null;
149395
+ const selfName = selfMemberName(target);
149396
+ if (selfName) {
149397
+ const typeDecl = enclosingTypeDecl(node2);
149398
+ if (typeDecl && enclosingTypeHasProperty(typeDecl, selfName))
149399
+ return null;
149400
+ }
148913
149401
  return makeViolation(this.ruleKey, node2, filePath, "low", "Redundant check against the value being assigned", `\`if (${condition.text}) ${assign.text};\` guards a plain assignment with a check against the same value \u2014 assigning unconditionally has the identical effect.`, sourceCode, "Remove the guard and assign unconditionally, or fix the condition if a different check was intended.");
148914
149402
  }
148915
149403
  };
@@ -149399,7 +149887,8 @@ var init_redundant_base_call = __esm({
149399
149887
  const receiver = node2.childForFieldName("expression");
149400
149888
  if (receiver?.type !== "base" && receiver?.text !== "base")
149401
149889
  return null;
149402
- const memberName3 = node2.childForFieldName("name")?.text;
149890
+ const nameNode = node2.childForFieldName("name");
149891
+ const memberName3 = nameNode?.type === "generic_name" ? nameNode.namedChildren.find((c) => c?.type === "identifier")?.text : nameNode?.text;
149403
149892
  if (!memberName3)
149404
149893
  return null;
149405
149894
  const typeDecl = enclosingType(node2);
@@ -149734,6 +150223,19 @@ function baseEntryName(entry) {
149734
150223
  }
149735
150224
  return null;
149736
150225
  }
150226
+ function baseEntryArity(entry) {
150227
+ const generic = entry.type === "generic_name" ? entry : entry.type === "qualified_name" ? entry.childForFieldName("name") : null;
150228
+ if (generic?.type !== "generic_name")
150229
+ return 0;
150230
+ const args = generic.namedChildren.find((c) => c?.type === "type_argument_list");
150231
+ return args ? args.namedChildren.filter((c) => c != null).length : 0;
150232
+ }
150233
+ function declaredArity(node2) {
150234
+ const params = node2.childForFieldName("type_parameters");
150235
+ if (!params)
150236
+ return 0;
150237
+ return params.namedChildren.filter((c) => c?.type === "type_parameter").length;
150238
+ }
149737
150239
  var csharpRecursiveTypeInheritanceVisitor;
149738
150240
  var init_recursive_type_inheritance = __esm({
149739
150241
  "packages/analyzer/dist/rules/bugs/visitors/csharp/recursive-type-inheritance.js"() {
@@ -149755,6 +150257,8 @@ var init_recursive_type_inheritance = __esm({
149755
150257
  return null;
149756
150258
  if (baseEntryName(firstBase) !== typeName2)
149757
150259
  return null;
150260
+ if (baseEntryArity(firstBase) !== declaredArity(node2))
150261
+ return null;
149758
150262
  return makeViolation(this.ruleKey, firstBase, filePath, "high", "Recursive type inheritance", `\`${typeName2}\` lists itself as its own base type, producing a nonsensical recursive inheritance chain.`, sourceCode, "Remove the self reference from the base list.");
149759
150263
  }
149760
150264
  };
@@ -151982,6 +152486,20 @@ function isCSharpGeneratedSource(filePath, sourceCode) {
151982
152486
  return true;
151983
152487
  return sourceCode.slice(0, 600).includes("<auto-generated");
151984
152488
  }
152489
+ function isCSharpEfMigration(node2) {
152490
+ const cls = getCSharpEnclosingClass(node2);
152491
+ if (!cls)
152492
+ return false;
152493
+ const baseList = cls.namedChildren.find((c) => c?.type === "base_list");
152494
+ if (!baseList)
152495
+ return false;
152496
+ return baseList.namedChildren.some((base) => {
152497
+ if (!base)
152498
+ return false;
152499
+ const simple = base.text.split(".").pop() ?? base.text;
152500
+ return simple === "Migration";
152501
+ });
152502
+ }
151985
152503
  function isCSharpPrivateMember(node2) {
151986
152504
  const accessibility = [];
151987
152505
  for (const child of node2.children) {
@@ -154545,17 +155063,13 @@ var init_collapsible_if3 = __esm({
154545
155063
  });
154546
155064
 
154547
155065
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/commented-out-code.js
154548
- var CODE_PATTERNS, csharpCommentedOutCodeVisitor;
154549
- var init_commented_out_code3 = __esm({
155066
+ var isLineComment3, csharpCommentedOutCodeVisitor;
155067
+ var init_commented_out_code4 = __esm({
154550
155068
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/commented-out-code.js"() {
154551
155069
  "use strict";
154552
155070
  init_types3();
154553
- CODE_PATTERNS = [
154554
- /^\s*(var|int|string|bool|double|decimal|long|float|return|if|for|foreach|while|using|throw|try|catch|switch|await|new|public|private|protected|internal|static)\s/,
154555
- /[;{}]\s*$/,
154556
- /=>/,
154557
- /\w+\(.*\)\s*[;{]?\s*$/
154558
- ];
155071
+ init_commented_out_code();
155072
+ isLineComment3 = (t) => t.startsWith("//") && !t.startsWith("///");
154559
155073
  csharpCommentedOutCodeVisitor = {
154560
155074
  ruleKey: "code-quality/deterministic/commented-out-code",
154561
155075
  languages: ["csharp"],
@@ -154564,15 +155078,19 @@ var init_commented_out_code3 = __esm({
154564
155078
  const text4 = node2.text;
154565
155079
  if (text4.startsWith("///") || text4.startsWith("/**"))
154566
155080
  return null;
154567
- let inner = text4;
154568
- if (inner.startsWith("//"))
154569
- inner = inner.slice(2).trim();
154570
- else if (inner.startsWith("/*"))
154571
- inner = inner.slice(2, -2).trim();
154572
- if (inner.length < 10)
155081
+ let inner;
155082
+ if (isLineComment3(text4)) {
155083
+ inner = lineCommentRunInner(node2, { isLineComment: isLineComment3, strip: (t) => t.replace(/^\/\/+/, "") });
155084
+ if (inner === null)
155085
+ return null;
155086
+ } else if (text4.startsWith("/*")) {
155087
+ inner = text4.slice(2, -2);
155088
+ } else {
155089
+ return null;
155090
+ }
155091
+ if (inner.trim().length < 10)
154573
155092
  return null;
154574
- const matchCount = CODE_PATTERNS.filter((p) => p.test(inner)).length;
154575
- if (matchCount >= 2) {
155093
+ if (isCommentedOutCode(inner, {})) {
154576
155094
  return makeViolation(this.ruleKey, node2, filePath, "low", "Commented-out code", "This comment appears to contain commented-out code. Remove it or track it in version control.", sourceCode, "Delete the commented-out code. If needed, it can be recovered from version control.");
154577
155095
  }
154578
155096
  return null;
@@ -155280,6 +155798,13 @@ function csharpEmptyBodyIsIntentional(node2, body) {
155280
155798
  }
155281
155799
  if (isCSharpNoOpCallbackPosition(node2))
155282
155800
  return true;
155801
+ let enclosingType3 = node2.parent;
155802
+ while (enclosingType3 && enclosingType3.type !== "class_declaration" && enclosingType3.type !== "struct_declaration" && enclosingType3.type !== "record_declaration" && enclosingType3.type !== "record_struct_declaration") {
155803
+ enclosingType3 = enclosingType3.parent;
155804
+ }
155805
+ const typeName2 = enclosingType3?.childForFieldName("name")?.text;
155806
+ if (typeName2 && /^(Null|Noop|NoOp|Empty)[A-Z0-9]/.test(typeName2))
155807
+ return true;
155283
155808
  if (node2.type === "method_declaration") {
155284
155809
  if (hasCSharpModifier(node2, "virtual") || hasCSharpModifier(node2, "override") || hasCSharpModifier(node2, "partial"))
155285
155810
  return true;
@@ -155557,6 +156082,7 @@ var init_hardcoded_url2 = __esm({
155557
156082
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/hardcoded-url.js"() {
155558
156083
  "use strict";
155559
156084
  init_types3();
156085
+ init_csharp_helpers();
155560
156086
  NAMESPACE_URI_HOSTS = /w3\.org|schema\.org|xmlns|openxmlformats|xmlsoap|purl\.org|tempuri\.org|schemas\.microsoft\.com|tools\.ietf\.org|datatracker\.ietf\.org|rfc-editor\.org/;
155561
156087
  STABLE_API_HOSTS = /googleapis\.com|maps\.google|api\.stripe\.com|api\.twilio\.com|api\.sendgrid\.com|api\.github\.com|cdn\.|fonts\.googleapis|cloudflare|unpkg\.com|cdnjs\.cloudflare|jsdelivr\.net|nuget\.org/;
155562
156088
  PLACEHOLDER_NAME = /default|placeholder|example|sample|template|fallback|demo|test|mock|stub|dummy/;
@@ -155568,6 +156094,8 @@ var init_hardcoded_url2 = __esm({
155568
156094
  const text4 = node2.text;
155569
156095
  if (!/https?:\/\/[a-zA-Z0-9]/.test(text4))
155570
156096
  return null;
156097
+ if (isCSharpTestPath(filePath))
156098
+ return null;
155571
156099
  if (text4.includes("example.com") || text4.includes("localhost") || text4.includes("127.0.0.1") || text4.includes("placeholder"))
155572
156100
  return null;
155573
156101
  if (NAMESPACE_URI_HOSTS.test(text4))
@@ -155588,6 +156116,42 @@ var init_hardcoded_url2 = __esm({
155588
156116
  });
155589
156117
 
155590
156118
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/identical-functions.js
156119
+ function isTrivialExpr(e) {
156120
+ switch (e.type) {
156121
+ case "integer_literal":
156122
+ case "real_literal":
156123
+ case "boolean_literal":
156124
+ case "string_literal":
156125
+ case "verbatim_string_literal":
156126
+ case "null_literal":
156127
+ case "character_literal":
156128
+ case "identifier":
156129
+ case "this_expression":
156130
+ case "default_expression":
156131
+ case "member_access_expression":
156132
+ return true;
156133
+ case "invocation_expression": {
156134
+ const args = e.childForFieldName("arguments");
156135
+ if (!args)
156136
+ return true;
156137
+ return args.namedChildren.every((a) => {
156138
+ if (a?.type !== "argument")
156139
+ return true;
156140
+ const value = a.namedChildren.find((c) => c && c.type !== "name_colon");
156141
+ return !value || isTrivialExpr(value);
156142
+ });
156143
+ }
156144
+ default:
156145
+ return false;
156146
+ }
156147
+ }
156148
+ function isTrivialReturnBody(body) {
156149
+ const stmts = body.namedChildren.filter((c) => c && c.type !== "comment");
156150
+ if (stmts.length !== 1 || stmts[0]?.type !== "return_statement")
156151
+ return false;
156152
+ const expr = stmts[0].namedChildren.find((c) => c && c.type !== "comment");
156153
+ return !expr || isTrivialExpr(expr);
156154
+ }
155591
156155
  var COMPARED_TYPES, csharpIdenticalFunctionsVisitor;
155592
156156
  var init_identical_functions2 = __esm({
155593
156157
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/identical-functions.js"() {
@@ -155606,7 +156170,7 @@ var init_identical_functions2 = __esm({
155606
156170
  const body = n.childForFieldName("body");
155607
156171
  if (body && body.type === "block" && body.namedChildCount > 0) {
155608
156172
  const onlyThrow = body.namedChildCount === 1 && body.namedChildren[0]?.type === "throw_statement";
155609
- if (!onlyThrow) {
156173
+ if (!onlyThrow && !isTrivialReturnBody(body)) {
155610
156174
  const normalized = body.text.replace(/\s+/g, " ").trim();
155611
156175
  bodies.push({ body: normalized, fnNode: n });
155612
156176
  }
@@ -155796,6 +156360,8 @@ var init_magic_number2 = __esm({
155796
156360
  return null;
155797
156361
  if (parentType === "argument" && parent.parent?.type === "bracketed_argument_list")
155798
156362
  return null;
156363
+ if (parentType === "argument" && parent.childForFieldName("name"))
156364
+ return null;
155799
156365
  if (parentType === "binary_expression" && TIME_FACTORS2.has(val)) {
155800
156366
  const op = parent.childForFieldName("operator");
155801
156367
  if (op?.text === "*") {
@@ -156117,6 +156683,8 @@ var init_no_extraneous_class2 = __esm({
156117
156683
  return null;
156118
156684
  if (node2.namedChildren.some((c) => c?.type === "base_list"))
156119
156685
  return null;
156686
+ if (node2.namedChildren.some((c) => c?.type === "attribute_list"))
156687
+ return null;
156120
156688
  const body = node2.childForFieldName("body");
156121
156689
  if (!body)
156122
156690
  return null;
@@ -156375,7 +156943,8 @@ var init_redundant_jump3 = __esm({
156375
156943
  if (!grandparent)
156376
156944
  return null;
156377
156945
  if (node2.type === "return_statement") {
156378
- if (node2.namedChildren.length > 0)
156946
+ const hasExpression = node2.children.some((c) => c != null && c.type !== "return" && c.type !== ";");
156947
+ if (hasExpression)
156379
156948
  return null;
156380
156949
  if (!isCSharpFunctionBoundary(grandparent.type))
156381
156950
  return null;
@@ -157740,6 +158309,8 @@ var init_too_many_lines3 = __esm({
157740
158309
  const bodyNode = getCSharpFunctionBody(node2);
157741
158310
  if (!bodyNode)
157742
158311
  return null;
158312
+ if (isCSharpGeneratedSource(filePath, sourceCode) || isCSharpEfMigration(node2))
158313
+ return null;
157743
158314
  const lineCount = bodyNode.endPosition.row - bodyNode.startPosition.row + 1;
157744
158315
  if (lineCount > 50) {
157745
158316
  const name = getCSharpFunctionName(node2);
@@ -158387,93 +158958,6 @@ var init_unused_collection2 = __esm({
158387
158958
  }
158388
158959
  });
158389
158960
 
158390
- // packages/analyzer/dist/rules/code-quality/visitors/csharp/unused-function-parameter.js
158391
- var csharpUnusedFunctionParameterVisitor;
158392
- var init_unused_function_parameter2 = __esm({
158393
- "packages/analyzer/dist/rules/code-quality/visitors/csharp/unused-function-parameter.js"() {
158394
- "use strict";
158395
- init_types3();
158396
- init_csharp_helpers();
158397
- csharpUnusedFunctionParameterVisitor = {
158398
- ruleKey: "code-quality/deterministic/unused-function-parameter",
158399
- languages: ["csharp"],
158400
- nodeTypes: ["method_declaration", "constructor_declaration", "local_function_statement"],
158401
- visit(node2, filePath, sourceCode) {
158402
- const params = node2.childForFieldName("parameters");
158403
- if (!params || params.namedChildCount === 0)
158404
- return null;
158405
- const body = node2.childForFieldName("body");
158406
- if (!body)
158407
- return null;
158408
- if (body.type === "block" && body.namedChildCount === 0)
158409
- return null;
158410
- if (/NotImplementedException|NotSupportedException/.test(body.text))
158411
- return null;
158412
- if (body.type === "block" && body.namedChildCount === 1 && body.namedChildren[0]?.type === "return_statement" && /Task\.CompletedTask|Task\.FromResult|ValueTask\.CompletedTask/.test(body.namedChildren[0].text)) {
158413
- return null;
158414
- }
158415
- if (node2.type === "method_declaration") {
158416
- for (const mod of ["override", "virtual", "abstract", "partial", "extern"]) {
158417
- if (hasCSharpModifier(node2, mod))
158418
- return null;
158419
- }
158420
- if (node2.namedChildren.some((c) => c?.type === "explicit_interface_specifier"))
158421
- return null;
158422
- if (node2.childForFieldName("name")?.text === "Main")
158423
- return null;
158424
- }
158425
- const paramList = [];
158426
- for (const param of params.namedChildren) {
158427
- if (!param || param.type !== "parameter")
158428
- continue;
158429
- if (param.children.some((c) => c?.type === "this" || c?.type === "modifier" && c.text === "this"))
158430
- continue;
158431
- if (param.childForFieldName("type")?.text === "CancellationToken")
158432
- continue;
158433
- const nameNode = param.childForFieldName("name");
158434
- if (nameNode?.type === "identifier")
158435
- paramList.push({ name: nameNode.text, node: param });
158436
- }
158437
- if (paramList.length === 0)
158438
- return null;
158439
- if (paramList.length === 2) {
158440
- const first2 = params.namedChildren.find((c) => c?.type === "parameter");
158441
- const firstType = first2?.childForFieldName("type")?.text;
158442
- const secondType = paramList[1] ? paramList[1].node.childForFieldName("type")?.text : void 0;
158443
- if ((firstType === "object" || firstType === "object?") && secondType?.endsWith("EventArgs"))
158444
- return null;
158445
- }
158446
- const used = /* @__PURE__ */ new Set();
158447
- function collectReads(n) {
158448
- if (n.type === "identifier") {
158449
- const parent = n.parent;
158450
- const isMemberName = parent && (parent.type === "member_access_expression" || parent.type === "qualified_name" || parent.type === "member_binding_expression") && parent.childForFieldName("name")?.id === n.id;
158451
- if (!isMemberName)
158452
- used.add(n.text);
158453
- }
158454
- for (let i = 0; i < n.namedChildCount; i++) {
158455
- const child = n.namedChild(i);
158456
- if (child)
158457
- collectReads(child);
158458
- }
158459
- }
158460
- collectReads(body);
158461
- const initializer = node2.namedChildren.find((c) => c?.type === "constructor_initializer");
158462
- if (initializer)
158463
- collectReads(initializer);
158464
- for (const { name, node: paramNode } of paramList) {
158465
- if (name.startsWith("_"))
158466
- continue;
158467
- if (!used.has(name)) {
158468
- return makeViolation(this.ruleKey, paramNode, filePath, "low", `Unused parameter \`${name}\``, `Parameter \`${name}\` is never used in the method body. Remove it or rename it to \`_\` if the signature is fixed.`, sourceCode, `Remove unused parameter \`${name}\` or rename it to \`_${name}\`.`);
158469
- }
158470
- }
158471
- return null;
158472
- }
158473
- };
158474
- }
158475
- });
158476
-
158477
158961
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/unused-private-member.js
158478
158962
  var csharpUnusedPrivateMemberVisitor;
158479
158963
  var init_unused_private_member2 = __esm({
@@ -158498,6 +158982,8 @@ var init_unused_private_member2 = __esm({
158498
158982
  for (const member of body.namedChildren) {
158499
158983
  if (!member)
158500
158984
  continue;
158985
+ if (member.namedChildren.some((c) => c?.type === "explicit_interface_specifier"))
158986
+ continue;
158501
158987
  if (!isCSharpPrivateMember(member))
158502
158988
  continue;
158503
158989
  if (getCSharpDeclAttributeNames(member).length > 0)
@@ -158895,8 +159381,17 @@ var init_abstract_class_public_constructor = __esm({
158895
159381
  });
158896
159382
 
158897
159383
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/abstract-class-without-abstract-members.js
158898
- function hasAbstractMember(body) {
158899
- return body.namedChildren.some((c) => c && MEMBER_TYPES2.has(c.type) && hasCSharpModifier(c, "abstract"));
159384
+ function hasMemberWithModifier(body, modifier) {
159385
+ return body.namedChildren.some((c) => c != null && MEMBER_TYPES2.has(c.type) && hasCSharpModifier(c, modifier));
159386
+ }
159387
+ function hasProtectedConstructor(body) {
159388
+ return body.namedChildren.some((c) => c?.type === "constructor_declaration" && hasCSharpModifier(c, "protected"));
159389
+ }
159390
+ function hasBaseList(classDecl) {
159391
+ return classDecl.namedChildren.some((c) => c?.type === "base_list");
159392
+ }
159393
+ function hasExtensionSignal(classDecl, body) {
159394
+ return hasMemberWithModifier(body, "abstract") || hasMemberWithModifier(body, "virtual") || hasMemberWithModifier(body, "override") || hasProtectedConstructor(body) || hasBaseList(classDecl);
158900
159395
  }
158901
159396
  var MEMBER_TYPES2, csharpAbstractClassWithoutAbstractMembersVisitor;
158902
159397
  var init_abstract_class_without_abstract_members = __esm({
@@ -158908,6 +159403,7 @@ var init_abstract_class_without_abstract_members = __esm({
158908
159403
  "method_declaration",
158909
159404
  "property_declaration",
158910
159405
  "event_declaration",
159406
+ "event_field_declaration",
158911
159407
  "indexer_declaration"
158912
159408
  ]);
158913
159409
  csharpAbstractClassWithoutAbstractMembersVisitor = {
@@ -158922,10 +159418,10 @@ var init_abstract_class_without_abstract_members = __esm({
158922
159418
  const body = node2.childForFieldName("body");
158923
159419
  if (!body)
158924
159420
  return null;
158925
- if (hasAbstractMember(body))
159421
+ if (hasExtensionSignal(node2, body))
158926
159422
  return null;
158927
159423
  const name = node2.childForFieldName("name")?.text ?? "class";
158928
- return makeViolation(this.ruleKey, node2, filePath, "low", "Abstract class without abstract members", `Abstract class \`${name}\` declares no abstract members, so it should be a concrete base class or an interface instead.`, sourceCode, "Add abstract members, drop the `abstract` modifier, or convert the type to an interface.");
159424
+ return makeViolation(this.ruleKey, node2, filePath, "low", "Abstract class without abstract members", `Abstract class \`${name}\` declares no abstract members and no other reason to be abstract (no virtual/override member, no protected constructor, no base type), so it should be a concrete class or a static holder instead.`, sourceCode, "Add abstract members, drop the `abstract` modifier, or convert the type to an interface.");
158929
159425
  }
158930
159426
  };
158931
159427
  }
@@ -159843,6 +160339,8 @@ var init_static_holder_type_has_constructor = __esm({
159843
160339
  return null;
159844
160340
  if (hasCSharpModifier(node2, "abstract"))
159845
160341
  return null;
160342
+ if (node2.namedChildren.some((c) => c?.type === "base_list"))
160343
+ return null;
159846
160344
  const body = node2.namedChildren.find((c) => c?.type === "declaration_list");
159847
160345
  if (!body)
159848
160346
  return null;
@@ -161726,6 +162224,15 @@ var init_extension_method_on_object = __esm({
161726
162224
  });
161727
162225
 
161728
162226
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/generic-parameter-not-inferable.js
162227
+ function isInterfaceMember(method) {
162228
+ let current = method.parent;
162229
+ while (current) {
162230
+ if (TYPE_DECL_TYPES2.has(current.type))
162231
+ return current.type === "interface_declaration";
162232
+ current = current.parent;
162233
+ }
162234
+ return false;
162235
+ }
161729
162236
  function identifiersIn2(node2) {
161730
162237
  const used = /* @__PURE__ */ new Set();
161731
162238
  if (!node2)
@@ -161747,14 +162254,23 @@ function parameterTypeIdentifiers(method) {
161747
162254
  for (const id of identifiersIn2(param.childForFieldName("type")))
161748
162255
  used.add(id);
161749
162256
  }
162257
+ for (const id of identifiersIn2(params.childForFieldName("type")))
162258
+ used.add(id);
161750
162259
  return used;
161751
162260
  }
161752
- var csharpGenericParameterNotInferableVisitor;
162261
+ var TYPE_DECL_TYPES2, csharpGenericParameterNotInferableVisitor;
161753
162262
  var init_generic_parameter_not_inferable = __esm({
161754
162263
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/generic-parameter-not-inferable.js"() {
161755
162264
  "use strict";
161756
162265
  init_types3();
161757
162266
  init_csharp_helpers();
162267
+ TYPE_DECL_TYPES2 = /* @__PURE__ */ new Set([
162268
+ "interface_declaration",
162269
+ "class_declaration",
162270
+ "struct_declaration",
162271
+ "record_declaration",
162272
+ "record_struct_declaration"
162273
+ ]);
161758
162274
  csharpGenericParameterNotInferableVisitor = {
161759
162275
  ruleKey: "code-quality/deterministic/generic-parameter-not-inferable",
161760
162276
  languages: ["csharp"],
@@ -161763,6 +162279,8 @@ var init_generic_parameter_not_inferable = __esm({
161763
162279
  const typeParams = node2.namedChildren.find((c) => c?.type === "type_parameter_list");
161764
162280
  if (!typeParams)
161765
162281
  return null;
162282
+ if (isInterfaceMember(node2))
162283
+ return null;
161766
162284
  const usedInParams = parameterTypeIdentifiers(node2);
161767
162285
  const nameNode = node2.childForFieldName("name");
161768
162286
  const usedInReturn = /* @__PURE__ */ new Set();
@@ -161774,15 +162292,13 @@ var init_generic_parameter_not_inferable = __esm({
161774
162292
  for (const id of identifiersIn2(child))
161775
162293
  usedInReturn.add(id);
161776
162294
  }
161777
- const usedInBody = identifiersIn2(node2.childForFieldName("body"));
161778
162295
  for (const tp of typeParams.namedChildren) {
161779
162296
  if (tp?.type !== "type_parameter")
161780
162297
  continue;
161781
162298
  const name = tp.namedChildren.find((c) => c?.type === "identifier")?.text;
161782
162299
  if (!name)
161783
162300
  continue;
161784
- const usedElsewhere = usedInReturn.has(name) || usedInBody.has(name);
161785
- if (!usedInParams.has(name) && usedElsewhere) {
162301
+ if (!usedInParams.has(name) && usedInReturn.has(name)) {
161786
162302
  return makeViolation(this.ruleKey, tp, filePath, "low", "Type parameter not inferable", `Type parameter \`${name}\` appears in no method parameter, so callers must always specify it explicitly.`, sourceCode, `Use \`${name}\` in a parameter type, or accept that callers must specify it and consider a non-generic design.`);
161787
162303
  }
161788
162304
  }
@@ -161895,6 +162411,9 @@ var init_native_method_not_wrapped = __esm({
161895
162411
  });
161896
162412
 
161897
162413
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/nested-generic-parameter.js
162414
+ function genericBaseName2(generic) {
162415
+ return generic.namedChildren.find((c) => c?.type === "identifier")?.text ?? "";
162416
+ }
161898
162417
  function asGenericName(typeNode) {
161899
162418
  if (!typeNode)
161900
162419
  return null;
@@ -161910,16 +162429,19 @@ function hasNestedGeneric(typeNode) {
161910
162429
  const generic = asGenericName(typeNode);
161911
162430
  if (!generic)
161912
162431
  return false;
162432
+ if (DELEGATE_OR_EXPRESSION_GENERICS.has(genericBaseName2(generic)))
162433
+ return false;
161913
162434
  const args = generic.namedChildren.find((c) => c?.type === "type_argument_list");
161914
162435
  if (!args)
161915
162436
  return false;
161916
162437
  return args.namedChildren.some((arg) => asGenericName(arg) !== null);
161917
162438
  }
161918
- var csharpNestedGenericParameterVisitor;
162439
+ var DELEGATE_OR_EXPRESSION_GENERICS, csharpNestedGenericParameterVisitor;
161919
162440
  var init_nested_generic_parameter = __esm({
161920
162441
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/nested-generic-parameter.js"() {
161921
162442
  "use strict";
161922
162443
  init_types3();
162444
+ DELEGATE_OR_EXPRESSION_GENERICS = /* @__PURE__ */ new Set(["Func", "Action", "Predicate", "Expression"]);
161923
162445
  csharpNestedGenericParameterVisitor = {
161924
162446
  ruleKey: "code-quality/deterministic/nested-generic-parameter",
161925
162447
  languages: ["csharp"],
@@ -162039,6 +162561,39 @@ function enclosingTypeBody(node2) {
162039
162561
  const body = node2.parent;
162040
162562
  return body?.type === "declaration_list" ? body : null;
162041
162563
  }
162564
+ function isZeroArgCallTo(expr, getName) {
162565
+ if (expr?.type !== "invocation_expression")
162566
+ return false;
162567
+ const fn = expr.childForFieldName("function") ?? expr.namedChildren[0];
162568
+ const fnText = fn?.text;
162569
+ if (fnText !== getName && fnText !== `this.${getName}`)
162570
+ return false;
162571
+ const args = expr.childForFieldName("arguments") ?? expr.namedChildren.find((c) => c?.type === "argument_list");
162572
+ const argCount = args?.namedChildren.filter((c) => c?.type === "argument").length ?? 0;
162573
+ return argCount === 0;
162574
+ }
162575
+ function getterDelegatesTo(prop, getName) {
162576
+ const propArrow = prop.namedChildren.find((c) => c?.type === "arrow_expression_clause");
162577
+ if (propArrow)
162578
+ return isZeroArgCallTo(propArrow.namedChildren.find(Boolean), getName);
162579
+ const accessorList = prop.namedChildren.find((c) => c?.type === "accessor_list");
162580
+ if (!accessorList)
162581
+ return false;
162582
+ const getter = accessorList.namedChildren.find((a) => a?.type === "accessor_declaration" && a.children.some((c) => c?.text === "get"));
162583
+ if (!getter)
162584
+ return false;
162585
+ const getArrow = getter.namedChildren.find((c) => c?.type === "arrow_expression_clause");
162586
+ if (getArrow)
162587
+ return isZeroArgCallTo(getArrow.namedChildren.find(Boolean), getName);
162588
+ const block = getter.namedChildren.find((c) => c?.type === "block");
162589
+ if (block) {
162590
+ const stmts = block.namedChildren.filter((c) => c?.type !== "comment");
162591
+ if (stmts.length === 1 && stmts[0]?.type === "return_statement") {
162592
+ return isZeroArgCallTo(stmts[0].namedChildren.find(Boolean), getName);
162593
+ }
162594
+ }
162595
+ return false;
162596
+ }
162042
162597
  var csharpPropertyNameMatchesGetMethodVisitor;
162043
162598
  var init_property_name_matches_get_method = __esm({
162044
162599
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/property-name-matches-get-method.js"() {
@@ -162066,6 +162621,8 @@ var init_property_name_matches_get_method = __esm({
162066
162621
  });
162067
162622
  if (!hasGetMethod)
162068
162623
  return null;
162624
+ if (getterDelegatesTo(node2, target))
162625
+ return null;
162069
162626
  return makeViolation(this.ruleKey, node2, filePath, "low", "Property name matches a Get method", `Property \`${propName}\` coexists with a parameterless \`${target}()\` method \u2014 a confusing duplication of how the value is exposed.`, sourceCode, `Keep one accessor: either the property \`${propName}\` or the method \`${target}\`, not both.`);
162070
162627
  }
162071
162628
  };
@@ -162073,6 +162630,50 @@ var init_property_name_matches_get_method = __esm({
162073
162630
  });
162074
162631
 
162075
162632
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/public-const-versioning-hazard.js
162633
+ function constFieldNames(fieldDecl) {
162634
+ const names = [];
162635
+ const varDecl = fieldDecl.namedChildren.find((c) => c?.type === "variable_declaration");
162636
+ if (!varDecl)
162637
+ return names;
162638
+ for (const vd of varDecl.namedChildren) {
162639
+ if (vd?.type !== "variable_declarator")
162640
+ continue;
162641
+ const n = vd.childForFieldName("name")?.text ?? vd.namedChildren.find((c) => c?.type === "identifier")?.text;
162642
+ if (n)
162643
+ names.push(n);
162644
+ }
162645
+ return names;
162646
+ }
162647
+ function referencedBySiblingConst(node2, names) {
162648
+ let root = node2;
162649
+ while (root.parent)
162650
+ root = root.parent;
162651
+ let found = false;
162652
+ const scan3 = (n) => {
162653
+ if (found)
162654
+ return;
162655
+ if (n.type === "field_declaration" && n.id !== node2.id && hasCSharpModifier(n, "const")) {
162656
+ const varDecl = n.namedChildren.find((c) => c?.type === "variable_declaration");
162657
+ if (varDecl) {
162658
+ for (const vd of varDecl.namedChildren) {
162659
+ if (vd?.type !== "variable_declarator")
162660
+ continue;
162661
+ const declaredName = vd.childForFieldName("name");
162662
+ walkCSharp(vd, (id) => {
162663
+ if (id.type === "identifier" && id.id !== declaredName?.id && names.includes(id.text)) {
162664
+ found = true;
162665
+ }
162666
+ });
162667
+ }
162668
+ }
162669
+ }
162670
+ for (const c of n.namedChildren)
162671
+ if (c)
162672
+ scan3(c);
162673
+ };
162674
+ scan3(root);
162675
+ return found;
162676
+ }
162076
162677
  var csharpPublicConstVersioningHazardVisitor;
162077
162678
  var init_public_const_versioning_hazard = __esm({
162078
162679
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/public-const-versioning-hazard.js"() {
@@ -162088,6 +162689,9 @@ var init_public_const_versioning_hazard = __esm({
162088
162689
  return null;
162089
162690
  if (!hasCSharpModifier(node2, "public") && !hasCSharpModifier(node2, "protected"))
162090
162691
  return null;
162692
+ const names = constFieldNames(node2);
162693
+ if (names.length > 0 && referencedBySiblingConst(node2, names))
162694
+ return null;
162091
162695
  return makeViolation(this.ruleKey, node2, filePath, "low", "Public constant versioning hazard", "A `public const` value is inlined into consumers at compile time and will not update unless they recompile \u2014 prefer `static readonly`.", sourceCode, "Replace the `public const` with a `public static readonly` field.");
162092
162696
  }
162093
162697
  };
@@ -162763,12 +163367,12 @@ var init_missing_access_modifier = __esm({
162763
163367
  return null;
162764
163368
  if (node2.parent?.parent?.type === "interface_declaration")
162765
163369
  return null;
163370
+ if (node2.namedChildren.some((c) => c?.type === "explicit_interface_specifier"))
163371
+ return null;
162766
163372
  if (node2.type === "method_declaration") {
162767
163373
  const nm = node2.childForFieldName("name");
162768
163374
  if (nm?.type === "qualified_name" || (nm?.text ?? "").includes("."))
162769
163375
  return null;
162770
- if (node2.namedChildren.some((c) => c?.type === "explicit_interface_specifier"))
162771
- return null;
162772
163376
  }
162773
163377
  if (node2.type === "constructor_declaration" && mods.includes("static"))
162774
163378
  return null;
@@ -162832,7 +163436,7 @@ function topLevelTypeIdentifiers(root) {
162832
163436
  const body = m.namedChildren.find((c) => c?.type === "declaration_list");
162833
163437
  if (body)
162834
163438
  collectFrom(body);
162835
- } else if (TYPE_DECL_TYPES2.has(m.type)) {
163439
+ } else if (TYPE_DECL_TYPES3.has(m.type)) {
162836
163440
  const id = m.childForFieldName("name");
162837
163441
  if (id)
162838
163442
  out.push(id);
@@ -162845,6 +163449,8 @@ function topLevelTypeIdentifiers(root) {
162845
163449
  function nameMatches(fileName, typeName2) {
162846
163450
  if (fileName === typeName2)
162847
163451
  return true;
163452
+ if (typeName2 === `${fileName}Attribute`)
163453
+ return true;
162848
163454
  const brace = fileName.indexOf("{");
162849
163455
  if (brace > 0 && fileName.slice(0, brace) === typeName2)
162850
163456
  return true;
@@ -162853,12 +163459,12 @@ function nameMatches(fileName, typeName2) {
162853
163459
  return true;
162854
163460
  return false;
162855
163461
  }
162856
- var TYPE_DECL_TYPES2, csharpFilenameTypeMismatchVisitor;
163462
+ var TYPE_DECL_TYPES3, csharpFilenameTypeMismatchVisitor;
162857
163463
  var init_csharp_filename_type_mismatch = __esm({
162858
163464
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/csharp-filename-type-mismatch.js"() {
162859
163465
  "use strict";
162860
163466
  init_types3();
162861
- TYPE_DECL_TYPES2 = /* @__PURE__ */ new Set([
163467
+ TYPE_DECL_TYPES3 = /* @__PURE__ */ new Set([
162862
163468
  "class_declaration",
162863
163469
  "struct_declaration",
162864
163470
  "interface_declaration",
@@ -162872,13 +163478,18 @@ var init_csharp_filename_type_mismatch = __esm({
162872
163478
  nodeTypes: ["compilation_unit"],
162873
163479
  visit(node2, filePath, sourceCode) {
162874
163480
  const base = filePath.split("/").pop() ?? "";
162875
- const fileName = base.replace(/\.[^.]*$/, "");
163481
+ const lower = base.toLowerCase();
163482
+ const compoundExt = [".cshtml.cs", ".razor.cs"].find((ext2) => lower.endsWith(ext2));
163483
+ const fileName = compoundExt ? base.slice(0, base.length - compoundExt.length) : base.replace(/\.[^.]*$/, "");
162876
163484
  if (!fileName)
162877
163485
  return null;
163486
+ const migrationName = fileName.match(/^\d{14}_(.+)$/)?.[1];
163487
+ const candidateNames2 = migrationName ? [fileName, migrationName] : [fileName];
162878
163488
  const identifiers = topLevelTypeIdentifiers(node2);
162879
163489
  if (identifiers.length === 0)
162880
163490
  return null;
162881
- if (identifiers.some((id) => nameMatches(fileName, id.text)))
163491
+ const matches = (id) => candidateNames2.some((fn) => nameMatches(fn, id.text)) || compoundExt !== void 0 && id.text === `${fileName}Model`;
163492
+ if (identifiers.some(matches))
162882
163493
  return null;
162883
163494
  const first2 = identifiers[0];
162884
163495
  return makeViolation(this.ruleKey, first2, filePath, "low", "File name does not match a contained type", `File '${fileName}' declares type '${first2.text}' but no top-level type matches the file name; name the file after a type so it is easy to locate.`, sourceCode, `Rename the file after a contained type, e.g. ${first2.text}.cs.`);
@@ -163331,7 +163942,7 @@ var init_csharp6 = __esm({
163331
163942
  init_cognitive_complexity3();
163332
163943
  init_collapsible_else_if2();
163333
163944
  init_collapsible_if3();
163334
- init_commented_out_code3();
163945
+ init_commented_out_code4();
163335
163946
  init_comparison_of_constant2();
163336
163947
  init_console_log2();
163337
163948
  init_contradictory_boolean_expression2();
@@ -163414,7 +164025,6 @@ var init_csharp6 = __esm({
163414
164025
  init_unnecessary_namespace_qualifier2();
163415
164026
  init_unsafe_any_usage2();
163416
164027
  init_unused_collection2();
163417
- init_unused_function_parameter2();
163418
164028
  init_unused_private_member2();
163419
164029
  init_unused_private_method2();
163420
164030
  init_unused_private_nested_class2();
@@ -163673,7 +164283,6 @@ var init_csharp6 = __esm({
163673
164283
  csharpUnnecessaryNamespaceQualifierVisitor,
163674
164284
  csharpUnsafeAnyUsageVisitor,
163675
164285
  csharpUnusedCollectionVisitor,
163676
- csharpUnusedFunctionParameterVisitor,
163677
164286
  csharpUnusedPrivateMemberVisitor,
163678
164287
  csharpUnusedPrivateMethodVisitor,
163679
164288
  csharpUnusedPrivateNestedClassVisitor,
@@ -165855,6 +166464,11 @@ function allElementsConstant(creation) {
165855
166464
  return false;
165856
166465
  return elements.every((e) => LITERAL_TYPES6.has(e.type));
165857
166466
  }
166467
+ function isGeneratedOrMigrationFile(filePath) {
166468
+ if (/(^|[\\/])Migrations[\\/]/.test(filePath))
166469
+ return true;
166470
+ return /\.(designer|g|g\.i|generated)\.cs$/i.test(filePath);
166471
+ }
165858
166472
  var LITERAL_TYPES6, csharpConstantArrayArgumentVisitor;
165859
166473
  var init_constant_array_argument = __esm({
165860
166474
  "packages/analyzer/dist/rules/performance/visitors/csharp/constant-array-argument.js"() {
@@ -165875,6 +166489,8 @@ var init_constant_array_argument = __esm({
165875
166489
  languages: ["csharp"],
165876
166490
  nodeTypes: ["array_creation_expression", "implicit_array_creation_expression"],
165877
166491
  visit(node2, filePath, sourceCode) {
166492
+ if (isGeneratedOrMigrationFile(filePath))
166493
+ return null;
165878
166494
  if (node2.parent?.type !== "argument")
165879
166495
  return null;
165880
166496
  if (!getCSharpEnclosingFunction(node2))
@@ -166663,7 +167279,36 @@ var init_missing_finally_cleanup2 = __esm({
166663
167279
  });
166664
167280
 
166665
167281
  // packages/analyzer/dist/rules/reliability/visitors/csharp/unsafe-json-parse.js
166666
- var JSON_PARSERS, csharpUnsafeJsonParseVisitor;
167282
+ function bodyDeclaresMember(body, name) {
167283
+ for (const member of body.namedChildren) {
167284
+ if (!member)
167285
+ continue;
167286
+ if (member.type === "property_declaration") {
167287
+ if (member.childForFieldName("name")?.text === name)
167288
+ return true;
167289
+ } else if (member.type === "field_declaration") {
167290
+ const decl = member.namedChildren.find((c) => c?.type === "variable_declaration");
167291
+ for (const d of decl?.namedChildren ?? []) {
167292
+ if (d?.type === "variable_declarator" && d.childForFieldName("name")?.text === name)
167293
+ return true;
167294
+ }
167295
+ }
167296
+ }
167297
+ return false;
167298
+ }
167299
+ function enclosingTypeDeclaresMember(node2, name) {
167300
+ let current = node2.parent;
167301
+ while (current) {
167302
+ if (TYPE_DECLS2.has(current.type)) {
167303
+ const body = current.childForFieldName("body");
167304
+ if (body && bodyDeclaresMember(body, name))
167305
+ return true;
167306
+ }
167307
+ current = current.parent;
167308
+ }
167309
+ return false;
167310
+ }
167311
+ var JSON_PARSERS, TYPE_DECLS2, csharpUnsafeJsonParseVisitor;
166667
167312
  var init_unsafe_json_parse3 = __esm({
166668
167313
  "packages/analyzer/dist/rules/reliability/visitors/csharp/unsafe-json-parse.js"() {
166669
167314
  "use strict";
@@ -166678,15 +167323,25 @@ var init_unsafe_json_parse3 = __esm({
166678
167323
  JsonDocument: /* @__PURE__ */ new Set(["Parse", "ParseAsync"]),
166679
167324
  JsonNode: /* @__PURE__ */ new Set(["Parse"])
166680
167325
  };
167326
+ TYPE_DECLS2 = /* @__PURE__ */ new Set([
167327
+ "class_declaration",
167328
+ "struct_declaration",
167329
+ "record_declaration",
167330
+ "record_struct_declaration"
167331
+ ]);
166681
167332
  csharpUnsafeJsonParseVisitor = {
166682
167333
  ruleKey: "reliability/deterministic/unsafe-json-parse",
166683
167334
  languages: ["csharp"],
166684
167335
  nodeTypes: ["invocation_expression"],
166685
167336
  visit(node2, filePath, sourceCode) {
166686
- const receiver = simpleTypeName5(getCSharpReceiver(node2));
167337
+ const rawReceiver = getCSharpReceiver(node2);
167338
+ const receiver = simpleTypeName5(rawReceiver);
166687
167339
  const method = getCSharpMethodName(node2);
166688
167340
  if (!JSON_PARSERS[receiver]?.has(method))
166689
167341
  return null;
167342
+ const bareReceiver = rawReceiver.startsWith("this.") ? rawReceiver.slice(5) : rawReceiver;
167343
+ if (!bareReceiver.includes(".") && enclosingTypeDeclaresMember(node2, bareReceiver))
167344
+ return null;
166690
167345
  const firstArg = getCSharpArguments(node2)[0];
166691
167346
  if (firstArg?.type === "invocation_expression") {
166692
167347
  const innerReceiver = simpleTypeName5(getCSharpReceiver(firstArg));
@@ -168393,18 +169048,10 @@ var init_logger_field_naming = __esm({
168393
169048
  csharpLoggerFieldNamingVisitor = {
168394
169049
  ruleKey: "style/deterministic/logger-field-naming",
168395
169050
  languages: ["csharp"],
168396
- nodeTypes: ["field_declaration", "property_declaration"],
169051
+ nodeTypes: ["field_declaration"],
168397
169052
  visit(node2, filePath, sourceCode) {
168398
169053
  if (isAutoGeneratedCSharpFile(sourceCode))
168399
169054
  return null;
168400
- if (node2.type === "property_declaration") {
168401
- if (!isLoggerType(node2.childForFieldName("type")))
168402
- return null;
168403
- const name = node2.childForFieldName("name");
168404
- if (!name || ACCEPTED.test(name.text))
168405
- return null;
168406
- return makeViolation(this.ruleKey, name, filePath, "low", "Logger does not follow the naming convention", `Logger '${name.text}' does not follow the convention for logger members (camelCase, optionally _-prefixed).`, sourceCode, `Rename the logger to a camelCase name such as '_logger'.`);
168407
- }
168408
169055
  const declaration = node2.namedChildren.find((c) => c?.type === "variable_declaration");
168409
169056
  if (!declaration)
168410
169057
  return null;
@@ -169212,15 +169859,28 @@ function isNestedInType(node2) {
169212
169859
  const parent = node2.parent;
169213
169860
  if (parent?.type !== "declaration_list")
169214
169861
  return false;
169215
- return parent.parent != null && TYPE_DECL_TYPES3.has(parent.parent.type);
169862
+ return parent.parent != null && TYPE_DECL_TYPES4.has(parent.parent.type);
169216
169863
  }
169217
- var TYPE_DECL_TYPES3, csharpNestedTypePubliclyVisibleVisitor;
169864
+ function isConstantContainer(node2) {
169865
+ if (node2.type !== "class_declaration")
169866
+ return false;
169867
+ if (!hasCSharpModifier(node2, "static"))
169868
+ return false;
169869
+ const body = node2.childForFieldName("body");
169870
+ if (!body)
169871
+ return false;
169872
+ const members = body.namedChildren.filter((c) => c && c.type !== "comment");
169873
+ if (members.length === 0)
169874
+ return false;
169875
+ return members.every((m) => m?.type === "field_declaration" && (hasCSharpModifier(m, "const") || hasCSharpModifier(m, "static") && hasCSharpModifier(m, "readonly")));
169876
+ }
169877
+ var TYPE_DECL_TYPES4, csharpNestedTypePubliclyVisibleVisitor;
169218
169878
  var init_nested_type_publicly_visible = __esm({
169219
169879
  "packages/analyzer/dist/rules/architecture/visitors/csharp/nested-type-publicly-visible.js"() {
169220
169880
  "use strict";
169221
169881
  init_types3();
169222
169882
  init_csharp_helpers();
169223
- TYPE_DECL_TYPES3 = /* @__PURE__ */ new Set([
169883
+ TYPE_DECL_TYPES4 = /* @__PURE__ */ new Set([
169224
169884
  "class_declaration",
169225
169885
  "struct_declaration",
169226
169886
  "interface_declaration",
@@ -169238,6 +169898,8 @@ var init_nested_type_publicly_visible = __esm({
169238
169898
  return null;
169239
169899
  if (getCSharpAttributeNames(node2).length > 0)
169240
169900
  return null;
169901
+ if (isConstantContainer(node2))
169902
+ return null;
169241
169903
  const name = node2.childForFieldName("name")?.text ?? "type";
169242
169904
  return makeViolation(this.ruleKey, node2, filePath, "low", "Publicly visible nested type", `Nested type '${name}' is publicly visible, exposing an implementation detail of its containing type.`, sourceCode, `Make '${name}' private or internal, or lift it out to its own top-level type if it is a real part of the API.`);
169243
169905
  }
@@ -169325,6 +169987,8 @@ var init_type_outside_namespace = __esm({
169325
169987
 
169326
169988
  // packages/analyzer/dist/rules/architecture/visitors/csharp/_uri-helpers.js
169327
169989
  function nameLooksLikeUri(name) {
169990
+ if (PREPOSITION_BEFORE_URI.test(name))
169991
+ return false;
169328
169992
  if (URI_NAME_PATTERN.test(name))
169329
169993
  return true;
169330
169994
  return /(Url|Uri|Endpoint|Webhook)$/.test(name);
@@ -169332,11 +169996,12 @@ function nameLooksLikeUri(name) {
169332
169996
  function isStringType(typeNode) {
169333
169997
  return typeNode?.type === "predefined_type" && typeNode.text === "string";
169334
169998
  }
169335
- var URI_NAME_PATTERN;
169999
+ var URI_NAME_PATTERN, PREPOSITION_BEFORE_URI;
169336
170000
  var init_uri_helpers = __esm({
169337
170001
  "packages/analyzer/dist/rules/architecture/visitors/csharp/_uri-helpers.js"() {
169338
170002
  "use strict";
169339
170003
  URI_NAME_PATTERN = /(^|[^a-z])(url|uri|uris|urls|endpoint|endpoints|webhook|webhookurl|callbackurl|redirecturi|redirecturl|hreflink|baseurl|baseaddress)$/i;
170004
+ PREPOSITION_BEFORE_URI = /(In|As|Of|At|By|Within)(Url|Uri|Uris|Urls|Endpoint|Endpoints|Webhook)$/;
169340
170005
  }
169341
170006
  });
169342
170007
 
@@ -169374,13 +170039,22 @@ var init_uri_parameter_as_string = __esm({
169374
170039
  });
169375
170040
 
169376
170041
  // packages/analyzer/dist/rules/architecture/visitors/csharp/uri-property-as-string.js
169377
- var csharpUriPropertyAsStringVisitor;
170042
+ function initializerProvesNotUri(valueNode) {
170043
+ if (!valueNode || !isCSharpStringNode(valueNode))
170044
+ return false;
170045
+ const text4 = getCSharpStringText(valueNode);
170046
+ if (text4 == null || text4.length === 0)
170047
+ return false;
170048
+ return !text4.includes("://");
170049
+ }
170050
+ var BINDING_ATTRIBUTES, csharpUriPropertyAsStringVisitor;
169378
170051
  var init_uri_property_as_string = __esm({
169379
170052
  "packages/analyzer/dist/rules/architecture/visitors/csharp/uri-property-as-string.js"() {
169380
170053
  "use strict";
169381
170054
  init_types3();
169382
170055
  init_csharp_helpers();
169383
170056
  init_uri_helpers();
170057
+ BINDING_ATTRIBUTES = /* @__PURE__ */ new Set(["BindProperty", "FromQuery", "FromRoute", "FromForm", "ModelBinder"]);
169384
170058
  csharpUriPropertyAsStringVisitor = {
169385
170059
  ruleKey: "architecture/deterministic/uri-property-as-string",
169386
170060
  languages: ["csharp"],
@@ -169393,6 +170067,10 @@ var init_uri_property_as_string = __esm({
169393
170067
  const name = node2.childForFieldName("name")?.text;
169394
170068
  if (!name || !nameLooksLikeUri(name))
169395
170069
  return null;
170070
+ if (getCSharpAttributeNames(node2).some((a) => BINDING_ATTRIBUTES.has(a)))
170071
+ return null;
170072
+ if (initializerProvesNotUri(node2.childForFieldName("value")))
170073
+ return null;
169396
170074
  return makeViolation(this.ruleKey, node2.childForFieldName("type"), filePath, "low", "URI property typed as string", `Property '${name}' holds a URI but is typed as string; use System.Uri to get parsing and validation.`, sourceCode, `Change the type of '${name}' from string to System.Uri.`);
169397
170075
  }
169398
170076
  };
@@ -169430,6 +170108,21 @@ function isModelType(classDecl) {
169430
170108
  const name = classDecl.childForFieldName("name")?.text ?? "";
169431
170109
  return MODEL_NAME_SUFFIX.test(name);
169432
170110
  }
170111
+ function isServerConstructed(classDecl, body) {
170112
+ for (const member of body.namedChildren) {
170113
+ if (member?.type === "constructor_declaration") {
170114
+ const params = member.childForFieldName("parameters");
170115
+ if (params && params.namedChildCount > 0)
170116
+ return true;
170117
+ }
170118
+ if (member?.type === "method_declaration" && hasCSharpModifier(member, "static")) {
170119
+ const mName = member.childForFieldName("name")?.text ?? "";
170120
+ if (mName === "Create" || mName.startsWith("From"))
170121
+ return true;
170122
+ }
170123
+ }
170124
+ return false;
170125
+ }
169433
170126
  function flaggablePropertyType(typeNode) {
169434
170127
  if (typeNode?.type !== "predefined_type")
169435
170128
  return null;
@@ -169442,7 +170135,7 @@ var init_value_type_action_param_under_posting = __esm({
169442
170135
  "use strict";
169443
170136
  init_types3();
169444
170137
  init_csharp_helpers();
169445
- MODEL_NAME_SUFFIX = /(Request|Dto|DTO|Model|ViewModel|Input|Form|Command|Query|Payload)$/;
170138
+ MODEL_NAME_SUFFIX = /(Request|Input|Form|Command|Query|Payload)$/;
169446
170139
  SCALAR_VALUE_TYPES = /* @__PURE__ */ new Set(["int", "long", "short", "byte", "uint", "ulong", "ushort", "sbyte", "decimal", "double", "float", "bool", "char"]);
169447
170140
  csharpValueTypeActionParamUnderPostingVisitor = {
169448
170141
  ruleKey: "architecture/deterministic/value-type-action-param-under-posting",
@@ -169454,6 +170147,8 @@ var init_value_type_action_param_under_posting = __esm({
169454
170147
  const body = node2.childForFieldName("body");
169455
170148
  if (!body)
169456
170149
  return null;
170150
+ if (isServerConstructed(node2, body))
170151
+ return null;
169457
170152
  for (const member of body.namedChildren) {
169458
170153
  if (member?.type !== "property_declaration")
169459
170154
  continue;
@@ -169481,6 +170176,22 @@ var init_value_type_action_param_under_posting = __esm({
169481
170176
  });
169482
170177
 
169483
170178
  // packages/analyzer/dist/rules/architecture/visitors/csharp/action-missing-producesresponsetype.js
170179
+ function excludedFromApiExplorer(node2) {
170180
+ for (const child of node2.children) {
170181
+ if (child?.type !== "attribute_list")
170182
+ continue;
170183
+ for (const attr of child.namedChildren) {
170184
+ if (attr?.type !== "attribute")
170185
+ continue;
170186
+ const name = (attr.childForFieldName("name")?.text ?? "").split(".").pop();
170187
+ if (name !== "ApiExplorerSettings")
170188
+ continue;
170189
+ if (/\bIgnoreApi\s*=\s*true\b/i.test(attr.text))
170190
+ return true;
170191
+ }
170192
+ }
170193
+ return false;
170194
+ }
169484
170195
  function attributeNames7(node2) {
169485
170196
  const names = [];
169486
170197
  for (const child of node2.children) {
@@ -169528,6 +170239,8 @@ var init_action_missing_producesresponsetype = __esm({
169528
170239
  const classAttrs = attributeNames7(cls);
169529
170240
  if (classAttrs.some((a) => a.startsWith("ProducesResponseType") || a === "ProducesDefaultResponseType"))
169530
170241
  return null;
170242
+ if (excludedFromApiExplorer(node2) || excludedFromApiExplorer(cls))
170243
+ return null;
169531
170244
  const name = node2.childForFieldName("name");
169532
170245
  return makeViolation(this.ruleKey, name ?? node2, filePath, "low", "Controller action has no [ProducesResponseType]", `Action '${name?.text ?? ""}' declares no [ProducesResponseType]; its response shapes and status codes are missing from the API description.`, sourceCode, "Annotate the action with [ProducesResponseType] for each status code it can return.");
169533
170246
  }
@@ -171470,52 +172183,96 @@ var init_pyproject_checker = __esm({
171470
172183
  // packages/analyzer/dist/roslyn-host-client.js
171471
172184
  import { spawn as spawn6 } from "child_process";
171472
172185
  import { existsSync as existsSync9 } from "fs";
172186
+ import { tmpdir } from "os";
171473
172187
  import { dirname as dirname8, resolve as resolve9 } from "path";
171474
172188
  import { fileURLToPath as fileURLToPath4 } from "url";
171475
172189
  function resolveRoslynHostBinary() {
171476
172190
  const override = process.env[HOST_BINARY_ENV];
171477
172191
  if (override)
171478
172192
  return existsSync9(override) ? override : null;
171479
- const here = dirname8(fileURLToPath4(import.meta.url));
171480
- const rel = "tools/csharp-roslyn-host/bin/Release/net8.0/csharp-roslyn-host";
171481
- for (const up of ["../../..", "../../../..", "../../../../.."]) {
171482
- const candidate = resolve9(here, up, rel);
171483
- if (existsSync9(candidate))
171484
- return candidate;
172193
+ const rels = [
172194
+ "roslyn-host/csharp-roslyn-host.dll",
172195
+ "tools/csharp-roslyn-host/bin/Release/net8.0/csharp-roslyn-host"
172196
+ ];
172197
+ let dir = dirname8(fileURLToPath4(import.meta.url));
172198
+ for (let i = 0; i < 12; i++) {
172199
+ for (const rel of rels) {
172200
+ const candidate = resolve9(dir, rel);
172201
+ if (existsSync9(candidate))
172202
+ return candidate;
172203
+ }
172204
+ const parent = dirname8(dir);
172205
+ if (parent === dir)
172206
+ break;
172207
+ dir = parent;
171485
172208
  }
171486
172209
  return null;
171487
172210
  }
171488
172211
  function invokeHost(bin, request) {
171489
172212
  return new Promise((resolvePromise, reject) => {
171490
- const child = spawn6(bin, [], { stdio: ["pipe", "pipe", "pipe"] });
172213
+ const viaDotnet = bin.endsWith(".dll");
172214
+ const cmd = viaDotnet ? "dotnet" : bin;
172215
+ const args = viaDotnet ? [bin] : [];
172216
+ const child = spawn6(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd: tmpdir() });
171491
172217
  let stdout = "";
171492
172218
  let stderr = "";
172219
+ let settled = false;
172220
+ const fail = (err) => {
172221
+ if (settled)
172222
+ return;
172223
+ settled = true;
172224
+ reject(err);
172225
+ };
172226
+ const succeed = (violations) => {
172227
+ if (settled)
172228
+ return;
172229
+ settled = true;
172230
+ resolvePromise(violations);
172231
+ };
171493
172232
  child.stdout.setEncoding("utf8");
171494
172233
  child.stderr.setEncoding("utf8");
171495
172234
  child.stdout.on("data", (d) => stdout += d);
171496
172235
  child.stderr.on("data", (d) => stderr += d);
171497
- child.on("error", (e) => reject(new RoslynHostUnavailableError(`Failed to start the Roslyn host (${bin}): ${e.message}. Is the .NET runtime installed?`)));
171498
- child.on("close", () => {
171499
- const line = stdout.split("\n").find((l) => l.trim());
171500
- if (!line) {
171501
- reject(new Error(`Roslyn host produced no output.${stderr ? ` stderr: ${stderr}` : ""}`));
172236
+ child.on("error", (e) => fail(new RoslynHostUnavailableError(`Failed to start the Roslyn host (${viaDotnet ? `dotnet ${bin}` : bin}): ${e.message}. Is the .NET runtime installed?`)));
172237
+ child.stdin.on("error", () => {
172238
+ });
172239
+ child.on("close", (code, signalName) => {
172240
+ if (code !== 0 || signalName) {
172241
+ const reason = stderr.trim() || stdout.trim() || "(no output)";
172242
+ fail(new RoslynHostUnavailableError(`The Roslyn host exited ${signalName ? `via ${signalName}` : `with code ${code}`} before responding. Is a compatible .NET runtime installed? Host output: ${reason}`));
172243
+ return;
172244
+ }
172245
+ const lines = stdout.split("\n").map((l) => l.trim()).filter(Boolean);
172246
+ if (lines.length === 0) {
172247
+ fail(new Error(`Roslyn host produced no output.${stderr ? ` stderr: ${stderr}` : ""}`));
171502
172248
  return;
171503
172249
  }
171504
172250
  let resp;
171505
- try {
171506
- resp = JSON.parse(line);
171507
- } catch {
171508
- reject(new Error(`Roslyn host returned invalid JSON: ${line}`));
172251
+ for (const l of lines) {
172252
+ try {
172253
+ const parsed = JSON.parse(l);
172254
+ if (parsed && typeof parsed.ok === "boolean") {
172255
+ resp = parsed;
172256
+ break;
172257
+ }
172258
+ } catch {
172259
+ }
172260
+ }
172261
+ if (!resp) {
172262
+ fail(new Error(`Roslyn host returned invalid JSON: ${lines[0]}`));
171509
172263
  return;
171510
172264
  }
171511
172265
  if (!resp.ok) {
171512
- reject(new Error(`Roslyn host error: ${resp.error ?? "unknown"}`));
172266
+ fail(new Error(`Roslyn host error: ${resp.error ?? "unknown"}`));
171513
172267
  return;
171514
172268
  }
171515
- resolvePromise(resp.violations ?? []);
172269
+ succeed(resp.violations ?? []);
171516
172270
  });
171517
- child.stdin.write(JSON.stringify(request) + "\n");
171518
- child.stdin.end();
172271
+ try {
172272
+ child.stdin.write(JSON.stringify(request) + "\n");
172273
+ child.stdin.end();
172274
+ } catch {
172275
+ }
171519
172276
  });
171520
172277
  }
171521
172278
  function runRoslynWorkspace(projectPath, rules) {
@@ -178074,7 +178831,7 @@ var init_schemas2 = __esm({
178074
178831
  // packages/core/dist/services/llm/cli-provider.js
178075
178832
  import { mkdirSync, writeFileSync } from "node:fs";
178076
178833
  import { join as join10 } from "node:path";
178077
- import { tmpdir } from "node:os";
178834
+ import { tmpdir as tmpdir2 } from "node:os";
178078
178835
  import { randomUUID as randomUUID23 } from "node:crypto";
178079
178836
  var import_cross_spawn, BaseCLIProvider, ClaudeCodeProvider;
178080
178837
  var init_cli_provider = __esm({
@@ -178144,7 +178901,7 @@ var init_cli_provider = __esm({
178144
178901
  constructor(transport) {
178145
178902
  this.transport = transport;
178146
178903
  if (process.env.TRUECOURSE_CLI_DEBUG) {
178147
- this.debugDir = join10(tmpdir(), "truecourse-cli-debug");
178904
+ this.debugDir = join10(tmpdir2(), "truecourse-cli-debug");
178148
178905
  mkdirSync(this.debugDir, { recursive: true });
178149
178906
  log.info(`[CLI] Debug output: ${this.debugDir}`);
178150
178907
  }
@@ -178257,8 +179014,13 @@ var init_cli_provider = __esm({
178257
179014
  unregisterChildProcess(this._repoId, child);
178258
179015
  reject(new Error(`[CLI] Failed to spawn ${this.binaryName}: ${err.message}`));
178259
179016
  });
178260
- child.stdin.write(prompt);
178261
- child.stdin.end();
179017
+ child.stdin.on("error", () => {
179018
+ });
179019
+ try {
179020
+ child.stdin.write(prompt);
179021
+ child.stdin.end();
179022
+ } catch {
179023
+ }
178262
179024
  });
178263
179025
  }
178264
179026
  /** Extract usage data from CLI JSON envelope if present. */
@@ -186525,7 +187287,7 @@ var DOMAIN_LABELS = {
186525
187287
  "database": "Database",
186526
187288
  "style": "Style"
186527
187289
  };
186528
- function buildAnalysisSteps(enabledCategories, enableLlmRules) {
187290
+ function buildAnalysisSteps(enabledCategories, enableLlmRules, hasCSharp) {
186529
187291
  const steps = [
186530
187292
  { key: "parse", label: "Parsing repository" }
186531
187293
  ];
@@ -186536,6 +187298,9 @@ function buildAnalysisSteps(enabledCategories, enableLlmRules) {
186536
187298
  for (const domain of activeDomains) {
186537
187299
  steps.push({ key: domain, label: `${DOMAIN_LABELS[domain]} checks` });
186538
187300
  }
187301
+ if (hasCSharp) {
187302
+ steps.push({ key: "csharp", label: "C# semantic analysis" });
187303
+ }
186539
187304
  steps.push({ key: "persist", label: "Saving results" });
186540
187305
  return steps;
186541
187306
  }
@@ -186562,6 +187327,23 @@ var StepTracker = class {
186562
187327
  this.emit();
186563
187328
  }
186564
187329
  }
187330
+ /**
187331
+ * Insert a step at runtime, just before `persist`, if it isn't already there.
187332
+ * Used for phases we can't declare up front — e.g. the C# semantic tier only
187333
+ * exists when the repo actually has C#, which isn't known until files are read.
187334
+ * No-op if the key already exists.
187335
+ */
187336
+ ensureStep(key4, label) {
187337
+ if (this.steps.some((s) => s.key === key4))
187338
+ return;
187339
+ const persistIdx = this.steps.findIndex((s) => s.key === "persist");
187340
+ const step = { key: key4, label, status: "pending" };
187341
+ if (persistIdx === -1)
187342
+ this.steps.push(step);
187343
+ else
187344
+ this.steps.splice(persistIdx, 0, step);
187345
+ this.emit();
187346
+ }
186565
187347
  setStatus(key4, status, detail) {
186566
187348
  const step = this.steps.find((s) => s.key === key4);
186567
187349
  if (step) {
@@ -195238,7 +196020,7 @@ import path17 from "node:path";
195238
196020
  function writeEvidence(params) {
195239
196021
  const dir = evidenceScenarioDir(params.repoRoot, params.runId, params.scenarioId);
195240
196022
  fs18.mkdirSync(dir, { recursive: true });
195241
- const failing = params.steps.find((s) => s.index === params.failingStep);
196023
+ const focus = params.failingStep != null ? params.steps.find((s) => s.index === params.failingStep) : params.steps[params.steps.length - 1];
195242
196024
  const invocation = {
195243
196025
  scenarioId: params.scenarioId,
195244
196026
  title: params.title,
@@ -195258,11 +196040,11 @@ function writeEvidence(params) {
195258
196040
  }))
195259
196041
  };
195260
196042
  writeFile(dir, "invocation.json", JSON.stringify(invocation, null, 2));
195261
- if (failing) {
195262
- writeFile(dir, "stdout.raw.txt", failing.rawStdout);
195263
- writeFile(dir, "stdout.txt", failing.normStdout);
195264
- writeFile(dir, "stderr.raw.txt", failing.rawStderr);
195265
- writeFile(dir, "stderr.txt", failing.normStderr);
196043
+ if (focus) {
196044
+ writeFile(dir, "stdout.raw.txt", focus.rawStdout);
196045
+ writeFile(dir, "stdout.txt", focus.normStdout);
196046
+ writeFile(dir, "stderr.raw.txt", focus.rawStderr);
196047
+ writeFile(dir, "stderr.txt", focus.normStderr);
195266
196048
  }
195267
196049
  const diffLines = [];
195268
196050
  if (params.outcome === "fail" && params.mismatch) {
@@ -195270,8 +196052,10 @@ function writeEvidence(params) {
195270
196052
  diffLines.push(`expected: ${params.mismatch.expected}`);
195271
196053
  diffLines.push(`actual: ${params.mismatch.actual}`, "");
195272
196054
  diffLines.push(...params.mismatch.detail);
195273
- } else if (params.infraMessage) {
196055
+ } else if (params.outcome === "error" && params.infraMessage) {
195274
196056
  diffLines.push(`step ${params.failingStep} \u2014 infrastructure error`, "", params.infraMessage);
196057
+ } else if (params.outcome === "pass") {
196058
+ diffLines.push(`all ${params.steps.length} step${params.steps.length === 1 ? "" : "s"} met their expectations`);
195275
196059
  }
195276
196060
  writeFile(dir, "diff.txt", diffLines.join("\n") + "\n");
195277
196061
  writeFile(dir, "files.txt", listSandboxFiles(params.sandboxCwd).join("\n") + "\n");
@@ -195385,7 +196169,7 @@ async function runScenario(scenario, ctx) {
195385
196169
  if (capture.spawnError || capture.timedOut) {
195386
196170
  const infra = capture.timedOut ? `step timed out after ${ctx.stepTimeoutMs}ms` : `failed to spawn: ${capture.spawnError}`;
195387
196171
  records.push(toRecord(stepIndex, argv, step.stdin, repeat3, iteration, capture, normText));
195388
- const evidencePath = writeEvidence({
196172
+ const evidencePath2 = writeEvidence({
195389
196173
  repoRoot: ctx.repoRoot,
195390
196174
  runId: ctx.runId,
195391
196175
  scenarioId: scenario.id,
@@ -195403,7 +196187,7 @@ async function runScenario(scenario, ctx) {
195403
196187
  outcome: "error",
195404
196188
  durationMs: Date.now() - start,
195405
196189
  failure: { step: stepIndex, expected: "the step to run", actual: infra },
195406
- evidencePath
196190
+ evidencePath: evidencePath2
195407
196191
  };
195408
196192
  }
195409
196193
  const normStdout = normText(capture.stdout);
@@ -195418,7 +196202,7 @@ async function runScenario(scenario, ctx) {
195418
196202
  });
195419
196203
  if (mismatch) {
195420
196204
  records.push(toRecord(stepIndex, argv, step.stdin, repeat3, iteration, capture, normText));
195421
- const evidencePath = writeEvidence({
196205
+ const evidencePath2 = writeEvidence({
195422
196206
  repoRoot: ctx.repoRoot,
195423
196207
  runId: ctx.runId,
195424
196208
  scenarioId: scenario.id,
@@ -195440,7 +196224,7 @@ async function runScenario(scenario, ctx) {
195440
196224
  expected: mismatch.expected,
195441
196225
  actual: mismatch.actual
195442
196226
  },
195443
- evidencePath
196227
+ evidencePath: evidencePath2
195444
196228
  };
195445
196229
  }
195446
196230
  }
@@ -195448,7 +196232,18 @@ async function runScenario(scenario, ctx) {
195448
196232
  records.push(toRecord(stepIndex, argv, step.stdin, repeat3, repeat3, lastCapture, normText));
195449
196233
  }
195450
196234
  }
195451
- return { ...base, outcome: "pass", durationMs: Date.now() - start };
196235
+ const evidencePath = ctx.capturePassEvidence ? writeEvidence({
196236
+ repoRoot: ctx.repoRoot,
196237
+ runId: ctx.runId,
196238
+ scenarioId: scenario.id,
196239
+ title: scenario.title,
196240
+ binds: scenario.binds,
196241
+ outcome: "pass",
196242
+ steps: records,
196243
+ sandboxCwd: sandbox.cwd,
196244
+ envPins: ENV_PINS
196245
+ }) : void 0;
196246
+ return { ...base, outcome: "pass", durationMs: Date.now() - start, ...evidencePath ? { evidencePath } : {} };
195452
196247
  } finally {
195453
196248
  sandbox.cleanup();
195454
196249
  }
@@ -195741,13 +196536,15 @@ async function runGuard(opts) {
195741
196536
  settled += 1;
195742
196537
  opts.onScenarioSettled?.(settled, selected.length, result);
195743
196538
  }
196539
+ const capturePassEvidence = opts.persist !== false;
195744
196540
  const executed = await mapWithConcurrency(executable, concurrency, async ({ scenario, resolution }) => {
195745
196541
  const outcome = await runScenario(scenario, {
195746
196542
  repoRoot,
195747
196543
  runId,
195748
196544
  resolvedEntry,
195749
196545
  recipeEnv: loaded.recipe.env,
195750
- stepTimeoutMs
196546
+ stepTimeoutMs,
196547
+ capturePassEvidence
195751
196548
  });
195752
196549
  const result = resolution.kind === "remap" ? { ...outcome, remappedTo: resolution.section.anchor } : outcome;
195753
196550
  settled += 1;
@@ -196365,7 +197162,16 @@ var OverlapSchema = external_exports.object({
196365
197162
  /** Short note on what may disagree ("auth0_id vs auth0_sub"). */
196366
197163
  note: external_exports.string().default(""),
196367
197164
  /** The specific conflicting sections per doc (markdown headings), when known. */
196368
- sections: external_exports.array(OverlapSectionSchema).default([])
197165
+ sections: external_exports.array(OverlapSectionSchema).default([]),
197166
+ /**
197167
+ * The area ids this dispute spans. Detection runs per area, so one disagreement
197168
+ * on a doc pair sharing several areas is flagged in each; the cross-area merge
197169
+ * collapses those to this single record and lists every area it spanned here, so
197170
+ * a resolution scoped to any of them clears the dispute everywhere. Empty for
197171
+ * older corpora written before the merge — the read layer recomputes the span
197172
+ * from the per-area placement of the (then-duplicated) records.
197173
+ */
197174
+ areas: external_exports.array(external_exports.string()).default([])
196369
197175
  });
196370
197176
  var AreaSchema = external_exports.object({
196371
197177
  /** Canonical area id, `product/concern`. */
@@ -197140,6 +197946,7 @@ init_zod();
197140
197946
  import { createHash as createHash6 } from "node:crypto";
197141
197947
  import fs28 from "node:fs";
197142
197948
  init_transport();
197949
+ init_dist();
197143
197950
  var DEFAULT_MAX_PAIRS_PER_AREA = 60;
197144
197951
  async function flagOverlaps(repoRoot, areas, docs, opts = {}) {
197145
197952
  const result = /* @__PURE__ */ new Map();
@@ -197211,7 +198018,7 @@ async function flagOverlaps(repoRoot, areas, docs, opts = {}) {
197211
198018
  examineOne(repoRoot, pair.areaId, pair.a, pair.b, runner2).then((verdict) => {
197212
198019
  if (verdict.overlap) {
197213
198020
  const list = result.get(pair.areaId) ?? [];
197214
- list.push({ docs: [pair.a.path, pair.b.path], note: verdict.note, sections: verdict.sections ?? [] });
198021
+ list.push({ docs: [pair.a.path, pair.b.path], note: verdict.note, sections: verdict.sections ?? [], areas: [] });
197215
198022
  result.set(pair.areaId, list);
197216
198023
  }
197217
198024
  }).catch(() => {
@@ -197229,19 +198036,13 @@ async function flagOverlaps(repoRoot, areas, docs, opts = {}) {
197229
198036
  };
197230
198037
  launch();
197231
198038
  });
197232
- const seenKeys = /* @__PURE__ */ new Set();
197233
- for (const areaId of [...result.keys()].sort()) {
197234
- const kept = result.get(areaId).filter((ov) => {
197235
- const key4 = overlapDedupKey(ov);
197236
- if (seenKeys.has(key4))
197237
- return false;
197238
- seenKeys.add(key4);
197239
- return true;
197240
- });
197241
- if (kept.length === 0)
197242
- result.delete(areaId);
197243
- else
197244
- result.set(areaId, kept);
198039
+ const entries = [...result].flatMap(([area, list]) => list.map((overlap) => ({ area, overlap })));
198040
+ const merged = dedupeCrossAreaOverlaps(entries);
198041
+ result.clear();
198042
+ for (const m of merged) {
198043
+ const list = result.get(m.area) ?? [];
198044
+ list.push({ ...m.overlap, areas: m.areas });
198045
+ result.set(m.area, list);
197245
198046
  }
197246
198047
  for (const [areaId, list] of result) {
197247
198048
  list.sort((x, y) => x.docs.join() < y.docs.join() ? -1 : 1);
@@ -197249,11 +198050,6 @@ async function flagOverlaps(repoRoot, areas, docs, opts = {}) {
197249
198050
  }
197250
198051
  return result;
197251
198052
  }
197252
- function overlapDedupKey(ov) {
197253
- const docs = [...ov.docs].sort().join("::");
197254
- const secs = (ov.sections ?? []).map((s) => `${s.doc}|${s.heading}`).sort().join("::");
197255
- return `${docs}##${secs}`;
197256
- }
197257
198053
  async function examineOne(repoRoot, areaId, a, b, runner2) {
197258
198054
  const cacheKey = computeCacheKey4(areaId, a, b);
197259
198055
  const cached = await readCache3(repoRoot, cacheKey);
@@ -205321,7 +206117,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
205321
206117
  "obj",
205322
206118
  ".vs"
205323
206119
  ]);
205324
- function isTestFile2(filePath) {
206120
+ function isTestFile3(filePath) {
205325
206121
  const parts = filePath.replace(/\\/g, "/").split("/");
205326
206122
  const fileName = parts[parts.length - 1] ?? "";
205327
206123
  if (/\.(test|spec)\.[mc]?[jt]sx?$/.test(fileName))
@@ -205355,7 +206151,7 @@ async function eachParsedSource(rootDir, visit) {
205355
206151
  }
205356
206152
  if (!entry.isFile())
205357
206153
  continue;
205358
- if (isTestFile2(full))
206154
+ if (isTestFile3(full))
205359
206155
  continue;
205360
206156
  const lang = EXT_TO_LANG[path33.extname(entry.name)];
205361
206157
  if (!lang)
@@ -205435,19 +206231,19 @@ function readCsharpString(node2, source) {
205435
206231
  }
205436
206232
  if (node2.type === "interpolated_string_expression") {
205437
206233
  let text4 = "";
205438
- let hasInterpolation2 = false;
206234
+ let hasInterpolation = false;
205439
206235
  for (let i = 0; i < node2.namedChildCount; i++) {
205440
206236
  const c = node2.namedChild(i);
205441
206237
  if (!c)
205442
206238
  continue;
205443
206239
  if (c.type === "interpolation") {
205444
206240
  text4 += " /*INTERP*/ ";
205445
- hasInterpolation2 = true;
206241
+ hasInterpolation = true;
205446
206242
  } else {
205447
206243
  text4 += source.slice(c.startIndex, c.endIndex);
205448
206244
  }
205449
206245
  }
205450
- return { text: text4, hasInterpolation: hasInterpolation2 };
206246
+ return { text: text4, hasInterpolation };
205451
206247
  }
205452
206248
  if (node2.type === "binary_expression") {
205453
206249
  const op = node2.childForFieldName("operator");
@@ -209370,14 +210166,14 @@ function getSqlText(node2, source) {
209370
210166
  return { text: source.slice(node2.startIndex, node2.endIndex), hasInterpolation: false };
209371
210167
  }
209372
210168
  function extractFromSqlSource(sqlNode, filePath, source) {
209373
- const { text: text4, hasInterpolation: hasInterpolation2 } = getSqlText(sqlNode, source);
209374
- return buildQueriesFromSqlText(text4, hasInterpolation2, {
210169
+ const { text: text4, hasInterpolation } = getSqlText(sqlNode, source);
210170
+ return buildQueriesFromSqlText(text4, hasInterpolation, {
209375
210171
  filePath,
209376
210172
  lineStart: sqlNode.startPosition.row + 1,
209377
210173
  lineEnd: sqlNode.endPosition.row + 1
209378
210174
  });
209379
210175
  }
209380
- function buildQueriesFromSqlText(text4, hasInterpolation2, loc, adapterName = "raw-sql") {
210176
+ function buildQueriesFromSqlText(text4, hasInterpolation, loc, adapterName = "raw-sql") {
209381
210177
  if (!text4 || !/\bSELECT\b/i.test(text4))
209382
210178
  return [];
209383
210179
  const blocks = splitSelectBlocks(text4);
@@ -209396,7 +210192,7 @@ function buildQueriesFromSqlText(text4, hasInterpolation2, loc, adapterName = "r
209396
210192
  else
209397
210193
  unparseable.push({ reason: "unrecognised SQL predicate", raw: piece });
209398
210194
  }
209399
- if (hasInterpolation2) {
210195
+ if (hasInterpolation) {
209400
210196
  unparseable.push({ reason: "string-interpolation substitution skipped", raw: "${...}" });
209401
210197
  }
209402
210198
  out.push({
@@ -209761,7 +210557,7 @@ function pySqlText(node2, source) {
209761
210557
  function readPyStringNode(node2, source) {
209762
210558
  if (node2.type === "concatenated_string") {
209763
210559
  let text5 = "";
209764
- let hasInterpolation3 = false;
210560
+ let hasInterpolation2 = false;
209765
210561
  for (let i = 0; i < node2.namedChildCount; i++) {
209766
210562
  const c = node2.namedChild(i);
209767
210563
  if (!c)
@@ -209769,15 +210565,15 @@ function readPyStringNode(node2, source) {
209769
210565
  const part = readPyStringNode(c, source);
209770
210566
  if (part) {
209771
210567
  text5 += part.text;
209772
- hasInterpolation3 = hasInterpolation3 || part.hasInterpolation;
210568
+ hasInterpolation2 = hasInterpolation2 || part.hasInterpolation;
209773
210569
  }
209774
210570
  }
209775
- return { text: text5, hasInterpolation: hasInterpolation3 };
210571
+ return { text: text5, hasInterpolation: hasInterpolation2 };
209776
210572
  }
209777
210573
  if (node2.type !== "string")
209778
210574
  return null;
209779
210575
  let text4 = "";
209780
- let hasInterpolation2 = false;
210576
+ let hasInterpolation = false;
209781
210577
  for (let i = 0; i < node2.namedChildCount; i++) {
209782
210578
  const c = node2.namedChild(i);
209783
210579
  if (!c)
@@ -209786,10 +210582,10 @@ function readPyStringNode(node2, source) {
209786
210582
  text4 += source.slice(c.startIndex, c.endIndex);
209787
210583
  else if (c.type === "interpolation") {
209788
210584
  text4 += " /*INTERP*/ ";
209789
- hasInterpolation2 = true;
210585
+ hasInterpolation = true;
209790
210586
  }
209791
210587
  }
209792
- return { text: text4, hasInterpolation: hasInterpolation2 };
210588
+ return { text: text4, hasInterpolation };
209793
210589
  }
209794
210590
 
209795
210591
  // packages/contract-verifier/dist/extractor/query/shared/python-orm.js
@@ -216899,7 +217695,7 @@ async function buildCodebaseScan(codeDir) {
216899
217695
 
216900
217696
  // packages/contract-verifier/dist/extractor/index.js
216901
217697
  var TS_EXT4 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
216902
- function isTestFile3(filePath) {
217698
+ function isTestFile4(filePath) {
216903
217699
  const parts = filePath.replace(/\\/g, "/").split("/");
216904
217700
  const fileName = parts[parts.length - 1];
216905
217701
  if (/\.(test|spec)\.(t|j)sx?$/.test(fileName))
@@ -216929,7 +217725,7 @@ async function extractOperationsFromDir(rootDir) {
216929
217725
  const ext2 = path44.extname(entry.name);
216930
217726
  if (!TS_EXT4.has(ext2))
216931
217727
  continue;
216932
- if (isTestFile3(full))
217728
+ if (isTestFile4(full))
216933
217729
  continue;
216934
217730
  const source = fs43.readFileSync(full, "utf-8");
216935
217731
  const lang = ext2 === ".ts" || ext2 === ".tsx" ? ext2 === ".tsx" ? "tsx" : "typescript" : "javascript";
@@ -226618,7 +227414,7 @@ var PROMPT_OVERHEAD_TOKENS = 500;
226618
227414
  var TOKENS_PER_RULE = 50;
226619
227415
  var TOKENS_PER_FILE_PATH = 25;
226620
227416
  var MAX_CHARS_PER_BATCH = 1e5;
226621
- function isTestFile4(filePath) {
227417
+ function isTestFile5(filePath) {
226622
227418
  return TEST_PATTERNS.some((p) => filePath.includes(p));
226623
227419
  }
226624
227420
  function matchesFileFilter(fa, filter2, fileContent) {
@@ -226653,7 +227449,7 @@ function matchesFileFilter(fa, filter2, fileContent) {
226653
227449
  return false;
226654
227450
  }
226655
227451
  if (filter2.isTestFile !== void 0) {
226656
- if (filter2.isTestFile !== isTestFile4(fa.filePath))
227452
+ if (filter2.isTestFile !== isTestFile5(fa.filePath))
226657
227453
  return false;
226658
227454
  }
226659
227455
  if (filter2.languages) {
@@ -231405,21 +232201,21 @@ function node(getModule) {
231405
232201
  if (lineMatch) {
231406
232202
  let object;
231407
232203
  let method;
231408
- let functionName;
232204
+ let functionName2;
231409
232205
  let typeName2;
231410
232206
  let methodName3;
231411
232207
  if (lineMatch[1]) {
231412
- functionName = lineMatch[1];
231413
- let methodStart = functionName.lastIndexOf(".");
231414
- if (functionName[methodStart - 1] === ".") {
232208
+ functionName2 = lineMatch[1];
232209
+ let methodStart = functionName2.lastIndexOf(".");
232210
+ if (functionName2[methodStart - 1] === ".") {
231415
232211
  methodStart--;
231416
232212
  }
231417
232213
  if (methodStart > 0) {
231418
- object = functionName.slice(0, methodStart);
231419
- method = functionName.slice(methodStart + 1);
232214
+ object = functionName2.slice(0, methodStart);
232215
+ method = functionName2.slice(methodStart + 1);
231420
232216
  const objectEnd = object.indexOf(".Module");
231421
232217
  if (objectEnd > 0) {
231422
- functionName = functionName.slice(objectEnd + 1);
232218
+ functionName2 = functionName2.slice(objectEnd + 1);
231423
232219
  object = object.slice(0, objectEnd);
231424
232220
  }
231425
232221
  }
@@ -231431,11 +232227,11 @@ function node(getModule) {
231431
232227
  }
231432
232228
  if (method === "<anonymous>") {
231433
232229
  methodName3 = void 0;
231434
- functionName = void 0;
232230
+ functionName2 = void 0;
231435
232231
  }
231436
- if (functionName === void 0) {
232232
+ if (functionName2 === void 0) {
231437
232233
  methodName3 = methodName3 || UNKNOWN_FUNCTION;
231438
- functionName = typeName2 ? `${typeName2}.${methodName3}` : methodName3;
232234
+ functionName2 = typeName2 ? `${typeName2}.${methodName3}` : methodName3;
231439
232235
  }
231440
232236
  let filename = lineMatch[2]?.startsWith("file://") ? lineMatch[2].slice(7) : lineMatch[2];
231441
232237
  const isNative = lineMatch[5] === "native";
@@ -231448,7 +232244,7 @@ function node(getModule) {
231448
232244
  return {
231449
232245
  filename: filename ? decodeURI(filename) : void 0,
231450
232246
  module: getModule ? getModule(filename) : void 0,
231451
- function: functionName,
232247
+ function: functionName2,
231452
232248
  lineno: _parseIntOrUndefined(lineMatch[3]),
231453
232249
  colno: _parseIntOrUndefined(lineMatch[4]),
231454
232250
  in_app: filenameIsInApp(filename || "", isNative),
@@ -231645,7 +232441,7 @@ function readToolVersion() {
231645
232441
  if (cachedVersion)
231646
232442
  return cachedVersion;
231647
232443
  if (true) {
231648
- cachedVersion = "0.7.0-next.4";
232444
+ cachedVersion = "0.7.0-next.6";
231649
232445
  return cachedVersion;
231650
232446
  }
231651
232447
  try {
@@ -234043,7 +234839,7 @@ function findCSharpProjectFiles(repoPath) {
234043
234839
  continue;
234044
234840
  walk20(path63.join(dir, e.name), depth + 1);
234045
234841
  } else if (e.isFile()) {
234046
- if (e.name.endsWith(".sln"))
234842
+ if (e.name.endsWith(".sln") || e.name.endsWith(".slnx"))
234047
234843
  solutions.push(path63.join(dir, e.name));
234048
234844
  else if (e.name.endsWith(".csproj"))
234049
234845
  projects.push(path63.join(dir, e.name));
@@ -234306,6 +235102,29 @@ async function runViolationPipeline(input) {
234306
235102
  }
234307
235103
  }
234308
235104
  }
235105
+ {
235106
+ const detByDomain = /* @__PURE__ */ new Map();
235107
+ for (const v of allCodeViolations) {
235108
+ const d = v.ruleKey.split("/")[0];
235109
+ detByDomain.set(d, (detByDomain.get(d) ?? 0) + 1);
235110
+ }
235111
+ const archDet = serviceViolationResults.length + moduleViolationResults.length + methodViolationResults.length;
235112
+ if (archDet > 0)
235113
+ detByDomain.set("architecture", (detByDomain.get("architecture") ?? 0) + archDet);
235114
+ for (const domain of DOMAIN_ORDER) {
235115
+ const count = detByDomain.get(domain) ?? 0;
235116
+ tracker?.done(domain, count > 0 ? `${count} violations` : "Clean");
235117
+ }
235118
+ }
235119
+ const detViolationCount = allCodeViolations.length;
235120
+ let csharpStepActive = false;
235121
+ const startCsharpStep = (detail) => {
235122
+ if (!csharpStepActive) {
235123
+ tracker?.ensureStep("csharp", "C# semantic analysis");
235124
+ csharpStepActive = true;
235125
+ }
235126
+ tracker?.start("csharp", detail);
235127
+ };
234309
235128
  const enabledHostRules = enabledCodeRules.filter((r) => r.engine === "roslyn-host");
234310
235129
  if (enabledHostRules.length > 0) {
234311
235130
  const csharpFiles = [];
@@ -234319,6 +235138,7 @@ async function runViolationPipeline(input) {
234319
235138
  csharpFiles.push({ path: key4, text: fc.content });
234320
235139
  }
234321
235140
  if (csharpFiles.length > 0) {
235141
+ startCsharpStep(`Analyzing ${csharpFiles.length} C# files\u2026`);
234322
235142
  const ruleByKey = new Map(enabledHostRules.map((r) => [r.key, r]));
234323
235143
  const hostViolations = await runRoslynHost(csharpFiles, enabledHostRules.map((r) => r.key));
234324
235144
  for (const v of hostViolations) {
@@ -234345,19 +235165,20 @@ async function runViolationPipeline(input) {
234345
235165
  if (enabledWorkspaceRules.length > 0 && filesToScan.some((f2) => detectLanguage(f2.filePath) === "csharp")) {
234346
235166
  const projectFiles = findCSharpProjectFiles(repoPath);
234347
235167
  if (projectFiles.length === 0) {
234348
- log.info("[Pipeline] C# present but no .csproj/.sln found \u2014 project-aware C# rules skipped");
235168
+ log.info("[Pipeline] C# present but no .csproj/.sln/.slnx found \u2014 project-aware C# rules skipped");
234349
235169
  } else {
234350
235170
  const ruleByKey = new Map(enabledWorkspaceRules.map((r) => [r.key, r]));
234351
235171
  const ruleKeys = enabledWorkspaceRules.map((r) => r.key);
234352
- for (const projectFile of projectFiles) {
235172
+ for (const [pi, projectFile] of projectFiles.entries()) {
235173
+ startCsharpStep(projectFiles.length > 1 ? `Loading project ${pi + 1}/${projectFiles.length}: ${path63.basename(projectFile)}\u2026` : `Loading project ${path63.basename(projectFile)}\u2026`);
234353
235174
  let wsViolations;
234354
235175
  try {
234355
235176
  wsViolations = await runRoslynWorkspace(projectFile, ruleKeys);
234356
235177
  } catch (err) {
235178
+ tracker?.error("csharp", `Project load failed: ${path63.basename(projectFile)}`);
234357
235179
  if (err instanceof RoslynHostUnavailableError)
234358
235180
  throw err;
234359
- log.warn(`[Pipeline] Project-aware C# rules skipped for ${path63.basename(projectFile)}: ${err.message}. Restore the project (\`dotnet restore\`) for full C# coverage.`);
234360
- continue;
235181
+ throw new Error(`Project-aware C# analysis failed for ${path63.basename(projectFile)}: ${err.message}. Restore the project (\`dotnet restore\`) and ensure its pinned .NET SDK is installed.`);
234361
235182
  }
234362
235183
  for (const v of wsViolations) {
234363
235184
  const rule = ruleByKey.get(v.ruleKey);
@@ -234389,6 +235210,10 @@ async function runViolationPipeline(input) {
234389
235210
  }
234390
235211
  }
234391
235212
  }
235213
+ if (csharpStepActive) {
235214
+ const csharpFindings = allCodeViolations.length - detViolationCount;
235215
+ tracker?.done("csharp", csharpFindings > 0 ? `${csharpFindings} violations` : "Clean");
235216
+ }
234392
235217
  log.info(`[Pipeline] Code scan: ${allCodeViolations.length} violations from ${filesToScan.length} files (${enabledCodeRules.length} det rules, ${enabledLlmCodeRules.length} LLM rules)`);
234393
235218
  if (enabledCodeRules.some((r) => r.key === "bugs/deterministic/invalid-pyproject-toml")) {
234394
235219
  const pyprojectPath = path63.join(repoPath, "pyproject.toml");
@@ -234434,10 +235259,6 @@ async function runViolationPipeline(input) {
234434
235259
  }
234435
235260
  const totalDet = [...violationsByDomain.values()].reduce((a, b) => a + b, 0);
234436
235261
  log.info(`[Pipeline] Totals: ${DOMAIN_ORDER.filter((d) => violationsByDomain.has(d)).map((d) => `${d}=${violationsByDomain.get(d)}`).join(", ")} (${totalDet})`);
234437
- for (const domain of DOMAIN_ORDER) {
234438
- const count = violationsByDomain.get(domain) ?? 0;
234439
- tracker?.done(domain, count > 0 ? `${count} violations` : "Clean");
234440
- }
234441
235262
  onProgress?.({ step: "analyzing", percent: 84, detail: "Code checks done" });
234442
235263
  const prevLlmCodeByFile = /* @__PURE__ */ new Map();
234443
235264
  for (const cv of previousActiveCodeViolations) {
@@ -239312,7 +240133,41 @@ var guard_default = router13;
239312
240133
  var import_express14 = __toESM(require_express2(), 1);
239313
240134
 
239314
240135
  // packages/core/dist/commands/guard-in-process.js
240136
+ init_dist();
239315
240137
  init_transport();
240138
+ var OpenConflictsError = class extends Error {
240139
+ conflicts;
240140
+ constructor(conflicts2) {
240141
+ super(formatOpenConflictsMessage(conflicts2));
240142
+ this.conflicts = conflicts2;
240143
+ this.name = "OpenConflictsError";
240144
+ }
240145
+ };
240146
+ function formatOpenConflictsMessage(conflicts2) {
240147
+ const lines = [
240148
+ `${conflicts2.length} open spec conflict${conflicts2.length === 1 ? "" : "s"} must be resolved before guard generate.`,
240149
+ "Extracting both sides of an unresolved overlap births a red finding that is really the dispute.",
240150
+ ""
240151
+ ];
240152
+ for (const c of conflicts2) {
240153
+ lines.push(` ${c.area}`);
240154
+ lines.push(` ${c.a} \u2194 ${c.b}`);
240155
+ if (c.note)
240156
+ lines.push(` ${c.note}`);
240157
+ }
240158
+ lines.push("");
240159
+ lines.push("Resolve them with `truecourse spec conflicts list` (or the dashboard Conflicts group), then re-run `truecourse guard generate`.");
240160
+ return lines.join("\n");
240161
+ }
240162
+ async function assertNoOpenConflicts(repoRoot) {
240163
+ const corpus = await getCorpus(repoRoot);
240164
+ if (!corpus)
240165
+ return;
240166
+ const decisions = await getDecisions(repoRoot);
240167
+ const open = openConflicts(corpus, decisions);
240168
+ if (open.length > 0)
240169
+ throw new OpenConflictsError(open);
240170
+ }
239316
240171
  var GUARD_GENERATE_STEPS = [
239317
240172
  { key: "index", label: "Indexing sections" },
239318
240173
  { key: "extract", label: "Extracting claims" },
@@ -239348,6 +240203,7 @@ function resolveTransport2(options) {
239348
240203
  }
239349
240204
  async function guardGenerateInProcess(repoRoot, options = {}) {
239350
240205
  const { tracker } = options;
240206
+ await assertNoOpenConflicts(repoRoot);
239351
240207
  if (options.onLlmEstimate) {
239352
240208
  const prices = await getModelPrices();
239353
240209
  const estimate = await estimateGuardTokens(repoRoot, prices);
@@ -239596,6 +240452,10 @@ router14.post("/:id/guard/generate", async (req, res, next) => {
239596
240452
  res.json({ cancelled: true });
239597
240453
  return;
239598
240454
  }
240455
+ if (e instanceof OpenConflictsError) {
240456
+ res.status(422).json({ error: e.message });
240457
+ return;
240458
+ }
239599
240459
  emitSpecProgress(repoId, { step: "error", percent: 100, detail: e.message });
239600
240460
  next(e);
239601
240461
  } finally {