truecourse 0.7.0-next.4 → 0.7.0-next.5

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 +1393 -682
  3. package/package.json +1 -1
  4. package/public/assets/{index-rfhWLMD4.js → index-CI8n7IHP.js} +231 -226
  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 +1018 -344
package/cli.mjs CHANGED
@@ -973,7 +973,7 @@ var require_command = __commonJS({
973
973
  var EventEmitter2 = __require("node:events").EventEmitter;
974
974
  var childProcess = __require("node:child_process");
975
975
  var path80 = __require("node:path");
976
- var fs70 = __require("node:fs");
976
+ var fs71 = __require("node:fs");
977
977
  var process2 = __require("node:process");
978
978
  var { Argument: Argument2, humanReadableArgName } = require_argument();
979
979
  var { CommanderError: CommanderError2 } = require_error();
@@ -1906,10 +1906,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
1906
1906
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1907
1907
  function findFile(baseDir, baseName) {
1908
1908
  const localBin = path80.resolve(baseDir, baseName);
1909
- if (fs70.existsSync(localBin)) return localBin;
1909
+ if (fs71.existsSync(localBin)) return localBin;
1910
1910
  if (sourceExt.includes(path80.extname(baseName))) return void 0;
1911
1911
  const foundExt = sourceExt.find(
1912
- (ext2) => fs70.existsSync(`${localBin}${ext2}`)
1912
+ (ext2) => fs71.existsSync(`${localBin}${ext2}`)
1913
1913
  );
1914
1914
  if (foundExt) return `${localBin}${foundExt}`;
1915
1915
  return void 0;
@@ -1921,7 +1921,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1921
1921
  if (this._scriptPath) {
1922
1922
  let resolvedScriptPath;
1923
1923
  try {
1924
- resolvedScriptPath = fs70.realpathSync(this._scriptPath);
1924
+ resolvedScriptPath = fs71.realpathSync(this._scriptPath);
1925
1925
  } catch (err) {
1926
1926
  resolvedScriptPath = this._scriptPath;
1927
1927
  }
@@ -17088,8 +17088,8 @@ function getMaxParameters(filePath) {
17088
17088
  }
17089
17089
  return 5;
17090
17090
  }
17091
- function isBootstrapEntry(functionName, filePath) {
17092
- return LANGUAGE_CONFIGS.some((c2) => c2.bootstrap.functionNames.includes(functionName) && c2.bootstrap.filePattern.test(filePath));
17091
+ function isBootstrapEntry(functionName2, filePath) {
17092
+ return LANGUAGE_CONFIGS.some((c2) => c2.bootstrap.functionNames.includes(functionName2) && c2.bootstrap.filePattern.test(filePath));
17093
17093
  }
17094
17094
  function getLanguageConfig(language) {
17095
17095
  const config2 = LANGUAGE_CONFIGS.find((c2) => c2.name === language);
@@ -21824,9 +21824,6 @@ function buildCSharpSymbolIndex(files, rootPath) {
21824
21824
  }
21825
21825
  function contributeCSharpEdges(files, rootPath) {
21826
21826
  const index = buildCSharpSymbolIndex(files, rootPath);
21827
- if (index.stats.ambiguousRefs > 0) {
21828
- console.warn(`[TrueCourse] C# symbol index: ${index.stats.ambiguousRefs} ambiguous type reference(s) skipped (same type name visible from multiple namespaces)`);
21829
- }
21830
21827
  return index.edges;
21831
21828
  }
21832
21829
  var TYPE_DECL_KINDS;
@@ -27155,12 +27152,9 @@ var init_lsp_client = __esm({
27155
27152
  this.process.stdout.on("data", (chunk3) => this.onData(chunk3));
27156
27153
  this.process.stderr.on("data", (chunk3) => {
27157
27154
  });
27158
- this.process.on("error", (err) => {
27159
- for (const pending of this.pendingRequests.values()) {
27160
- pending.reject(err);
27161
- }
27162
- this.pendingRequests.clear();
27163
- });
27155
+ this.process.on("error", (err) => this.rejectAllPending(err));
27156
+ this.process.stdin.on("error", (err) => this.rejectAllPending(new Error(`${this.config.name} LSP stdin error: ${err.message}`)));
27157
+ this.process.on("exit", (code, signal) => this.rejectAllPending(new Error(`${this.config.name} LSP server exited (${signal ?? `code ${code}`}) before responding`)));
27164
27158
  const initResult = await this.sendRequest("initialize", {
27165
27159
  processId: process.pid,
27166
27160
  capabilities: {
@@ -27339,17 +27333,33 @@ var init_lsp_client = __esm({
27339
27333
  // -------------------------------------------------------------------------
27340
27334
  // Internal: JSON-RPC transport
27341
27335
  // -------------------------------------------------------------------------
27336
+ /** Reject and clear every in-flight request — used when the server dies. */
27337
+ rejectAllPending(err) {
27338
+ if (this.pendingRequests.size === 0)
27339
+ return;
27340
+ for (const pending of this.pendingRequests.values())
27341
+ pending.reject(err);
27342
+ this.pendingRequests.clear();
27343
+ }
27342
27344
  sendRequest(method, params) {
27343
27345
  return new Promise((resolve12, reject) => {
27344
27346
  const id = ++this.requestId;
27345
27347
  this.pendingRequests.set(id, { resolve: resolve12, reject });
27346
27348
  const msg = { jsonrpc: "2.0", id, method, params };
27347
- this.process.stdin.write(encodeMessage(msg));
27349
+ try {
27350
+ this.process.stdin.write(encodeMessage(msg));
27351
+ } catch (err) {
27352
+ this.pendingRequests.delete(id);
27353
+ reject(err instanceof Error ? err : new Error(String(err)));
27354
+ }
27348
27355
  });
27349
27356
  }
27350
27357
  sendNotification(method, params) {
27351
27358
  const msg = { jsonrpc: "2.0", method, params };
27352
- this.process.stdin.write(encodeMessage(msg));
27359
+ try {
27360
+ this.process.stdin.write(encodeMessage(msg));
27361
+ } catch {
27362
+ }
27353
27363
  }
27354
27364
  onData(chunk3) {
27355
27365
  this.buffer = Buffer.concat([this.buffer, chunk3]);
@@ -37834,7 +37844,8 @@ var init_deterministic4 = __esm({
37834
37844
  description: "Function parameter is never referenced in the function body \u2014 remove it or prefix with `_`.",
37835
37845
  enabled: true,
37836
37846
  severity: "low",
37837
- type: "deterministic"
37847
+ type: "deterministic",
37848
+ engine: "roslyn-host"
37838
37849
  },
37839
37850
  {
37840
37851
  key: "code-quality/deterministic/hardcoded-url",
@@ -49118,6 +49129,94 @@ var init_production_debug_enabled = __esm({
49118
49129
  });
49119
49130
 
49120
49131
  // packages/analyzer/dist/rules/security/visitors/javascript/insecure-random.js
49132
+ function isSecuritySensitiveName(name) {
49133
+ const lower = name.toLowerCase();
49134
+ return SECURITY_KEYWORDS.some((kw) => lower.includes(kw));
49135
+ }
49136
+ function securityContextName(callNode) {
49137
+ let parent = callNode.parent;
49138
+ while (parent) {
49139
+ switch (parent.type) {
49140
+ case "variable_declarator":
49141
+ return leafName(parent.childForFieldName("name"));
49142
+ case "assignment_expression":
49143
+ case "augmented_assignment_expression":
49144
+ return leafName(parent.childForFieldName("left"));
49145
+ case "pair":
49146
+ return leafName(parent.childForFieldName("key"));
49147
+ case "public_field_definition":
49148
+ case "field_definition":
49149
+ return leafName(parent.childForFieldName("property") ?? parent.childForFieldName("name"));
49150
+ // The value is returned from the enclosing function (e.g.
49151
+ // `function generateToken() { return Math.random()... }`) — the
49152
+ // function name is the security context.
49153
+ case "return_statement":
49154
+ return enclosingFunctionName(parent);
49155
+ // Expression-bodied arrow returns the value directly, e.g.
49156
+ // `const genToken = () => Math.random()...`.
49157
+ case "arrow_function":
49158
+ return functionName(parent);
49159
+ // Structural boundaries: the random value is discarded or otherwise
49160
+ // not bound to a name.
49161
+ case "expression_statement":
49162
+ case "statement_block":
49163
+ case "if_statement":
49164
+ case "for_statement":
49165
+ case "for_in_statement":
49166
+ case "while_statement":
49167
+ case "do_statement":
49168
+ case "switch_statement":
49169
+ case "function_declaration":
49170
+ case "function_expression":
49171
+ case "method_definition":
49172
+ return null;
49173
+ }
49174
+ parent = parent.parent;
49175
+ }
49176
+ return null;
49177
+ }
49178
+ function enclosingFunctionName(node2) {
49179
+ let p2 = node2.parent;
49180
+ while (p2) {
49181
+ if (p2.type === "function_declaration" || p2.type === "generator_function_declaration" || p2.type === "method_definition" || p2.type === "arrow_function" || p2.type === "function_expression") {
49182
+ return functionName(p2);
49183
+ }
49184
+ p2 = p2.parent;
49185
+ }
49186
+ return null;
49187
+ }
49188
+ function functionName(fn) {
49189
+ const nameField = fn.childForFieldName("name");
49190
+ if (nameField)
49191
+ return leafName(nameField);
49192
+ const p2 = fn.parent;
49193
+ if (!p2)
49194
+ return null;
49195
+ if (p2.type === "variable_declarator")
49196
+ return leafName(p2.childForFieldName("name"));
49197
+ if (p2.type === "pair")
49198
+ return leafName(p2.childForFieldName("key"));
49199
+ if (p2.type === "assignment_expression")
49200
+ return leafName(p2.childForFieldName("left"));
49201
+ if (p2.type === "public_field_definition" || p2.type === "field_definition") {
49202
+ return leafName(p2.childForFieldName("property") ?? p2.childForFieldName("name"));
49203
+ }
49204
+ return null;
49205
+ }
49206
+ function leafName(node2) {
49207
+ if (!node2)
49208
+ return null;
49209
+ if (node2.type === "identifier" || node2.type === "property_identifier" || node2.type === "shorthand_property_identifier") {
49210
+ return node2.text;
49211
+ }
49212
+ if (node2.type === "member_expression") {
49213
+ return node2.childForFieldName("property")?.text ?? null;
49214
+ }
49215
+ if (node2.type === "string") {
49216
+ return node2.text.slice(1, -1);
49217
+ }
49218
+ return node2.text;
49219
+ }
49121
49220
  function isFixtureOrTestFile(filePath) {
49122
49221
  const lower = filePath.toLowerCase();
49123
49222
  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);
@@ -49148,19 +49247,9 @@ var init_insecure_random = __esm({
49148
49247
  if (/\.length\b/.test(parentText))
49149
49248
  return null;
49150
49249
  }
49151
- let parent = node2.parent;
49152
- while (parent) {
49153
- const parentText = parent.text.toLowerCase();
49154
- if (SECURITY_KEYWORDS.some((kw) => parentText.includes(kw))) {
49155
- const immediateParent = node2.parent;
49156
- if (immediateParent?.type === "binary_expression" && /\.length\b/.test(immediateParent.text)) {
49157
- return null;
49158
- }
49159
- 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.");
49160
- }
49161
- if (parent.type === "expression_statement" || parent.type === "variable_declaration" || parent.type === "assignment_expression" || parent.type === "lexical_declaration")
49162
- break;
49163
- parent = parent.parent;
49250
+ const contextName = securityContextName(node2);
49251
+ if (contextName && isSecuritySensitiveName(contextName)) {
49252
+ 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.");
49164
49253
  }
49165
49254
  }
49166
49255
  }
@@ -49589,6 +49678,11 @@ var init_unverified_cross_origin_message = __esm({
49589
49678
  }
49590
49679
  if (methodName3 !== "addEventListener")
49591
49680
  return null;
49681
+ if (fn.type === "member_expression") {
49682
+ const receiver = fn.childForFieldName("object")?.text ?? "";
49683
+ if (receiver !== "window" && receiver !== "self" && receiver !== "globalThis")
49684
+ return null;
49685
+ }
49592
49686
  const args = node2.childForFieldName("arguments");
49593
49687
  if (!args)
49594
49688
  return null;
@@ -50071,7 +50165,11 @@ var init_timing_attack_comparison = __esm({
50071
50165
  return null;
50072
50166
  const leftLeaf = leafIdentifier(left);
50073
50167
  const rightLeaf = leafIdentifier(right);
50074
- if (SENSITIVE_COMPARISON_PATTERNS.test(leftLeaf) || SENSITIVE_COMPARISON_PATTERNS.test(rightLeaf)) {
50168
+ const leftIsLiteral = left.type === "string" || left.type === "template_string";
50169
+ const rightIsLiteral = right.type === "string" || right.type === "template_string";
50170
+ const leftMatches = !leftIsLiteral && SENSITIVE_COMPARISON_PATTERNS.test(leftLeaf);
50171
+ const rightMatches = !rightIsLiteral && SENSITIVE_COMPARISON_PATTERNS.test(rightLeaf);
50172
+ if (leftMatches || rightMatches) {
50075
50173
  if (sourceCode.includes("timingSafeEqual"))
50076
50174
  return null;
50077
50175
  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.");
@@ -50658,6 +50756,14 @@ var init_mixed_http_methods = __esm({
50658
50756
  });
50659
50757
 
50660
50758
  // packages/analyzer/dist/rules/security/visitors/javascript/process-signaling.js
50759
+ function isOwnedProcessPid(node2) {
50760
+ if (node2.type !== "member_expression")
50761
+ return false;
50762
+ if (node2.childForFieldName("property")?.text !== "pid")
50763
+ return false;
50764
+ const obj = node2.childForFieldName("object");
50765
+ return obj?.type === "member_expression" && obj.childForFieldName("property")?.text === "process";
50766
+ }
50661
50767
  var processSignalingVisitor;
50662
50768
  var init_process_signaling = __esm({
50663
50769
  "packages/analyzer/dist/rules/security/visitors/javascript/process-signaling.js"() {
@@ -50685,6 +50791,8 @@ var init_process_signaling = __esm({
50685
50791
  const pidArg = args.namedChildren[0];
50686
50792
  if (!pidArg)
50687
50793
  return null;
50794
+ if (isOwnedProcessPid(pidArg))
50795
+ return null;
50688
50796
  if (pidArg.type !== "number") {
50689
50797
  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().");
50690
50798
  }
@@ -52038,8 +52146,8 @@ var init_sql_injection2 = __esm({
52038
52146
  if (!firstArg)
52039
52147
  return null;
52040
52148
  if (firstArg.type === "string" && firstArg.text.startsWith("f")) {
52041
- const hasInterpolation2 = firstArg.namedChildren.some((c2) => c2.type === "interpolation");
52042
- if (hasInterpolation2) {
52149
+ const hasInterpolation = firstArg.namedChildren.some((c2) => c2.type === "interpolation");
52150
+ if (hasInterpolation) {
52043
52151
  const queryText = firstArg.text;
52044
52152
  if (/:[a-z_]\w*|%s|%\(\w+\)s/.test(queryText))
52045
52153
  return null;
@@ -52549,17 +52657,17 @@ var init_eval_usage2 = __esm({
52549
52657
  funcName = attr.text;
52550
52658
  const receiver = fn.childForFieldName("object");
52551
52659
  if (receiver) {
52552
- let leafName2 = null;
52660
+ let leafName3 = null;
52553
52661
  if (receiver.type === "identifier") {
52554
- leafName2 = receiver.text;
52662
+ leafName3 = receiver.text;
52555
52663
  } else if (receiver.type === "attribute") {
52556
52664
  const attrChild = receiver.childForFieldName("attribute");
52557
52665
  if (attrChild) {
52558
- leafName2 = attrChild.text.replace(/^_+/, "");
52666
+ leafName3 = attrChild.text.replace(/^_+/, "");
52559
52667
  }
52560
52668
  }
52561
- if (leafName2 && SAFE_METHOD_RECEIVERS.has(leafName2)) {
52562
- const expectedSources = SAFE_RECEIVER_IMPORT_SOURCES[leafName2];
52669
+ if (leafName3 && SAFE_METHOD_RECEIVERS.has(leafName3)) {
52670
+ const expectedSources = SAFE_RECEIVER_IMPORT_SOURCES[leafName3];
52563
52671
  if (expectedSources) {
52564
52672
  const sources = getPythonImportSources(node2);
52565
52673
  for (const src of sources) {
@@ -56531,6 +56639,12 @@ var init_exclusions = __esm({
56531
56639
  /^https?:\/\/(?!hooks\.slack\.com|[a-z0-9]+\.webhook\.office\.com)/,
56532
56640
  // Docker / container image references
56533
56641
  /^(?:docker|ghcr|quay|gcr|ecr)\.io\//,
56642
+ // PostHog public project API key (phc_…) — intentionally client-exposed,
56643
+ // embedded in frontend code to send analytics events. Its 40+ char base62
56644
+ // body collides with the generic Cohere-token shape, but it is not a
56645
+ // private credential. Private PostHog keys use the phx_/phs_ prefixes and
56646
+ // are deliberately NOT allowlisted here.
56647
+ /^phc_[A-Za-z0-9]{30,}$/,
56534
56648
  // Common config patterns that aren't secrets
56535
56649
  /^\*+$/,
56536
56650
  // All asterisks (masked values)
@@ -58346,10 +58460,33 @@ var init_secret_scanner = __esm({
58346
58460
  });
58347
58461
 
58348
58462
  // packages/analyzer/dist/rules/security/visitors/universal.js
58463
+ function hasPasswordToken(text4) {
58464
+ const lower = text4.toLowerCase();
58465
+ return lower.includes("password") || lower.includes("passwd");
58466
+ }
58467
+ function isPasswordHashingContext(node2) {
58468
+ let stmt = node2.parent;
58469
+ while (stmt && !STATEMENT_NODE_TYPES2.has(stmt.type) && !FUNCTION_NODE_TYPES3.has(stmt.type)) {
58470
+ stmt = stmt.parent;
58471
+ }
58472
+ if (stmt && STATEMENT_NODE_TYPES2.has(stmt.type) && hasPasswordToken(stmt.text))
58473
+ return true;
58474
+ let fnNode = node2.parent;
58475
+ while (fnNode && !FUNCTION_NODE_TYPES3.has(fnNode.type))
58476
+ fnNode = fnNode.parent;
58477
+ if (fnNode) {
58478
+ const nameNode = fnNode.childForFieldName("name");
58479
+ const paramsNode = fnNode.childForFieldName("parameters") ?? fnNode.childForFieldName("parameter_list");
58480
+ const signature = `${nameNode?.text ?? ""} ${paramsNode?.text ?? ""}`;
58481
+ if (hasPasswordToken(signature))
58482
+ return true;
58483
+ }
58484
+ return false;
58485
+ }
58349
58486
  function stripCommentMarkers(text4) {
58350
58487
  return text4.replace(/^\/{2,3}\s?/, "").replace(/^\/\*\s?/, "").replace(/\s?\*\/$/, "").replace(/^\s*\*\s?/gm, "").replace(/^#\s?/gm, "").trim();
58351
58488
  }
58352
- 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;
58489
+ 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;
58353
58490
  var init_universal = __esm({
58354
58491
  "packages/analyzer/dist/rules/security/visitors/universal.js"() {
58355
58492
  "use strict";
@@ -59529,6 +59666,29 @@ var init_universal = __esm({
59529
59666
  "sha256",
59530
59667
  "sha512"
59531
59668
  ]);
59669
+ STATEMENT_NODE_TYPES2 = /* @__PURE__ */ new Set([
59670
+ "expression_statement",
59671
+ "return_statement",
59672
+ "lexical_declaration",
59673
+ "variable_declaration",
59674
+ "variable_declarator",
59675
+ "local_declaration_statement",
59676
+ "assignment",
59677
+ "augmented_assignment",
59678
+ "assignment_expression",
59679
+ "augmented_assignment_expression"
59680
+ ]);
59681
+ FUNCTION_NODE_TYPES3 = /* @__PURE__ */ new Set([
59682
+ "function_declaration",
59683
+ "function_expression",
59684
+ "arrow_function",
59685
+ "generator_function_declaration",
59686
+ "method_definition",
59687
+ "function_definition",
59688
+ "method_declaration",
59689
+ "local_function_statement",
59690
+ "constructor_declaration"
59691
+ ]);
59532
59692
  unpredictableSaltMissingVisitor = {
59533
59693
  ruleKey: "security/deterministic/unpredictable-salt-missing",
59534
59694
  nodeTypes: ["call_expression", "call", "invocation_expression"],
@@ -59546,21 +59706,12 @@ var init_universal = __esm({
59546
59706
  }
59547
59707
  if (!HASH_FUNCTIONS_NEEDING_SALT.has(methodName3))
59548
59708
  return null;
59549
- let parent = node2.parent;
59550
- while (parent) {
59551
- const parentText = parent.text.toLowerCase();
59552
- if (parentText.includes("password") || parentText.includes("passwd")) {
59553
- const args = node2.childForFieldName("arguments");
59554
- if (args && args.namedChildren.length < 2) {
59555
- 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.");
59556
- }
59557
- return null;
59558
- }
59559
- if (parent.type === "function_declaration" || parent.type === "function_definition" || parent.type === "method_definition" || parent.type === "program" || parent.type === "method_declaration" || parent.type === "compilation_unit")
59560
- break;
59561
- parent = parent.parent;
59562
- }
59563
- return null;
59709
+ const args = node2.childForFieldName("arguments");
59710
+ if (!args || args.namedChildren.length >= 2)
59711
+ return null;
59712
+ if (!isPasswordHashingContext(node2))
59713
+ return null;
59714
+ 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.");
59564
59715
  }
59565
59716
  };
59566
59717
  sensitiveFileVisitor = {
@@ -59653,7 +59804,7 @@ function isLiteralNode(n) {
59653
59804
  }
59654
59805
  return false;
59655
59806
  }
59656
- 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;
59807
+ 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;
59657
59808
  var init_helpers2 = __esm({
59658
59809
  "packages/analyzer/dist/rules/bugs/visitors/javascript/_helpers.js"() {
59659
59810
  "use strict";
@@ -59711,6 +59862,7 @@ var init_helpers2 = __esm({
59711
59862
  "reduce",
59712
59863
  "reduceRight"
59713
59864
  ]);
59865
+ SOCKET_IO_RECEIVERS = /* @__PURE__ */ new Set(["socket", "sockets"]);
59714
59866
  KNOWN_ARG_ORDERS = [
59715
59867
  { fn: "startsWith", params: [["prefix", "str", "string", "start", "needle", "search"]] },
59716
59868
  { fn: "endsWith", params: [["suffix", "str", "string", "end", "needle", "search"]] },
@@ -60347,6 +60499,15 @@ var init_loss_of_precision = __esm({
60347
60499
  const num = Number(text4);
60348
60500
  if (!Number.isFinite(num))
60349
60501
  return null;
60502
+ const parent = node2.parent;
60503
+ if (parent?.type === "arguments") {
60504
+ const call = parent.parent;
60505
+ if (call?.type === "call_expression") {
60506
+ const fn = call.childForFieldName("function");
60507
+ if (fn?.type === "identifier" && fn.text === "BigInt")
60508
+ return null;
60509
+ }
60510
+ }
60350
60511
  if (!Number.isSafeInteger(num)) {
60351
60512
  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.`);
60352
60513
  }
@@ -62618,6 +62779,17 @@ var init_useless_increment = __esm({
62618
62779
  });
62619
62780
 
62620
62781
  // packages/analyzer/dist/rules/bugs/visitors/javascript/ignored-return-value.js
62782
+ function receiverBaseName(obj) {
62783
+ if (!obj)
62784
+ return null;
62785
+ if (obj.type === "identifier")
62786
+ return obj.text.toLowerCase();
62787
+ if (obj.type === "member_expression") {
62788
+ const prop = obj.childForFieldName("property");
62789
+ return prop ? prop.text.toLowerCase() : null;
62790
+ }
62791
+ return null;
62792
+ }
62621
62793
  var ignoredReturnValueVisitor;
62622
62794
  var init_ignored_return_value = __esm({
62623
62795
  "packages/analyzer/dist/rules/bugs/visitors/javascript/ignored-return-value.js"() {
@@ -62638,6 +62810,11 @@ var init_ignored_return_value = __esm({
62638
62810
  const prop = fn.childForFieldName("property");
62639
62811
  if (!prop || !PURE_ARRAY_METHODS.has(prop.text))
62640
62812
  return null;
62813
+ if (prop.text === "join") {
62814
+ const base2 = receiverBaseName(fn.childForFieldName("object"));
62815
+ if (base2 && SOCKET_IO_RECEIVERS.has(base2))
62816
+ return null;
62817
+ }
62641
62818
  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}(...)\`.`);
62642
62819
  }
62643
62820
  };
@@ -63299,7 +63476,12 @@ var init_global_this_usage = __esm({
63299
63476
  let current = node2.parent;
63300
63477
  while (current) {
63301
63478
  const t2 = current.type;
63302
- if (t2 === "function_declaration" || t2 === "function" || t2 === "method_definition" || t2 === "class_declaration" || t2 === "class") {
63479
+ if (t2 === "function_declaration" || t2 === "function" || // Modern tree-sitter-javascript names a `function () {}` expression
63480
+ // `function_expression` (only the legacy grammar used bare `function`).
63481
+ // Without it, `this` inside any function-expression callback / IIFE /
63482
+ // prototype-assigned method walks up to program scope and is wrongly
63483
+ // reported as a global reference.
63484
+ t2 === "function_expression" || t2 === "generator_function" || t2 === "generator_function_declaration" || t2 === "method_definition" || t2 === "class_declaration" || t2 === "class") {
63303
63485
  return null;
63304
63486
  }
63305
63487
  if (t2 === "arrow_function") {
@@ -65965,6 +66147,9 @@ var init_missing_await = __esm({
65965
66147
  return null;
65966
66148
  if (!isInsideAsyncFunction(node2))
65967
66149
  return null;
66150
+ const declaredName = node2.childForFieldName("name");
66151
+ if (declaredName?.type === "identifier" && /Promise$/.test(declaredName.text))
66152
+ return null;
65968
66153
  const isPromise = typeQuery.isPromiseLike(filePath, value.startPosition.row, value.startPosition.column, value.endPosition.row, value.endPosition.column);
65969
66154
  if (!isPromise)
65970
66155
  return null;
@@ -66541,25 +66726,26 @@ var init_unbound_method = __esm({
66541
66726
  let classBody = node2.parent;
66542
66727
  while (classBody && classBody.type !== "class_body")
66543
66728
  classBody = classBody.parent;
66544
- if (classBody) {
66545
- for (const member of classBody.namedChildren) {
66546
- if (member.type === "public_field_definition" || member.type === "field_definition") {
66547
- const nameNode = member.children.find((c2) => c2.type === "property_identifier" || c2.type === "private_property_identifier");
66548
- if (nameNode?.text !== methodName3)
66549
- continue;
66550
- const value = member.childForFieldName("value");
66551
- if (value?.type === "arrow_function" || value?.type === "function_expression")
66552
- return null;
66553
- if (value)
66554
- return null;
66555
- const typeAnnot = member.namedChildren.find((c2) => c2.type === "type_annotation");
66556
- const typeChild = typeAnnot?.namedChildren[0];
66557
- if (typeChild && typeChild.type !== "function_type" && typeChild.type !== "constructor_type") {
66558
- return null;
66559
- }
66560
- }
66729
+ if (!classBody)
66730
+ return null;
66731
+ let resolvesToMethod = false;
66732
+ for (const member of classBody.namedChildren) {
66733
+ if (member.type === "method_definition") {
66734
+ const nameNode = member.childForFieldName("name");
66735
+ if (nameNode?.text !== methodName3)
66736
+ continue;
66737
+ const isAccessor = member.children.some((c2) => (c2.text === "get" || c2.text === "set") && c2.type !== "property_identifier");
66738
+ resolvesToMethod = !isAccessor;
66739
+ break;
66740
+ }
66741
+ if (member.type === "public_field_definition" || member.type === "field_definition") {
66742
+ const nameNode = member.children.find((c2) => c2.type === "property_identifier" || c2.type === "private_property_identifier");
66743
+ if (nameNode?.text === methodName3)
66744
+ return null;
66561
66745
  }
66562
66746
  }
66747
+ if (!resolvesToMethod)
66748
+ return null;
66563
66749
  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.");
66564
66750
  }
66565
66751
  };
@@ -66662,7 +66848,7 @@ var init_missing_error_boundary = __esm({
66662
66848
  visit(node2, filePath, sourceCode) {
66663
66849
  if (!sourceCode.includes("React") && !sourceCode.includes("react") && !sourceCode.includes("jsx"))
66664
66850
  return null;
66665
- const hasAsyncData = /(?<![.\w])useQuery\b/.test(sourceCode) || /(?<![.\w])useSWR\b/.test(sourceCode) || /\buseSuspenseQuery\b/.test(sourceCode) || /\buseSuspenseInfiniteQuery\b/.test(sourceCode) || /\bSuspense\b/.test(sourceCode);
66851
+ const hasAsyncData = /(?<![.\w])useQuery\b/.test(sourceCode) || /(?<![.\w])useSWR\b/.test(sourceCode) || /\buseSuspenseQuery\b/.test(sourceCode) || /\buseSuspenseInfiniteQuery\b/.test(sourceCode);
66666
66852
  if (!hasAsyncData)
66667
66853
  return null;
66668
66854
  if (/\bErrorBoundary\b/.test(sourceCode))
@@ -79487,6 +79673,9 @@ var init_star_import = __esm({
79487
79673
  if (sourceText === "zod" || sourceText.startsWith("@react-email/") || sourceText.startsWith("fumadocs-ui/") || sourceText.startsWith("fumadocs-core/")) {
79488
79674
  return null;
79489
79675
  }
79676
+ if (sourceText === "semver" || sourceText.startsWith("@sentry/")) {
79677
+ return null;
79678
+ }
79490
79679
  if (sourceText.startsWith("./") || sourceText.startsWith("../"))
79491
79680
  return null;
79492
79681
  const nsImport = node2.namedChildren.find((c2) => c2.type === "import_clause")?.namedChildren.find((cc) => cc.type === "namespace_import");
@@ -81055,14 +81244,20 @@ var init_expression_complexity = __esm({
81055
81244
  if (!expr)
81056
81245
  return null;
81057
81246
  let target = expr;
81058
- while (target.type === "parenthesized_expression" && target.namedChildren[0]) {
81247
+ while ((target.type === "parenthesized_expression" || target.type === "as_expression" || target.type === "satisfies_expression" || target.type === "non_null_expression") && target.namedChildren[0]) {
81059
81248
  target = target.namedChildren[0];
81060
81249
  }
81250
+ if (target.type === "object" || target.type === "array")
81251
+ return null;
81061
81252
  if (target.type === "arrow_function" || target.type === "function_expression" || target.type === "function_declaration" || target.type === "generator_function" || target.type === "generator_function_declaration")
81062
81253
  return null;
81063
81254
  if (target.type === "jsx_element" || target.type === "jsx_fragment" || target.type === "jsx_self_closing_element")
81064
81255
  return null;
81065
81256
  let operatorCount = 0;
81257
+ let hasLogical = false;
81258
+ let hasArithmetic = false;
81259
+ const LOGICAL_OPS = /* @__PURE__ */ new Set(["&&", "||", "??"]);
81260
+ const ARITHMETIC_OPS3 = /* @__PURE__ */ new Set(["+", "-", "*", "/", "%", "**", "&", "|", "^", "<<", ">>", ">>>"]);
81066
81261
  const BINARY_TYPES = /* @__PURE__ */ new Set(["binary_expression", "logical_expression"]);
81067
81262
  const FUNCTION_BOUNDARY_TYPES3 = /* @__PURE__ */ new Set([
81068
81263
  "function_declaration",
@@ -81075,6 +81270,13 @@ var init_expression_complexity = __esm({
81075
81270
  function countOps(n) {
81076
81271
  if (BINARY_TYPES.has(n.type)) {
81077
81272
  operatorCount++;
81273
+ const op = n.childForFieldName("operator")?.text;
81274
+ if (op) {
81275
+ if (LOGICAL_OPS.has(op))
81276
+ hasLogical = true;
81277
+ else if (ARITHMETIC_OPS3.has(op))
81278
+ hasArithmetic = true;
81279
+ }
81078
81280
  }
81079
81281
  for (let i = 0; i < n.childCount; i++) {
81080
81282
  const child = n.child(i);
@@ -81087,6 +81289,8 @@ var init_expression_complexity = __esm({
81087
81289
  }
81088
81290
  countOps(expr);
81089
81291
  if (operatorCount > 5) {
81292
+ if (hasLogical && !hasArithmetic)
81293
+ return null;
81090
81294
  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.");
81091
81295
  }
81092
81296
  return null;
@@ -81911,12 +82115,73 @@ var init_strict_equality = __esm({
81911
82115
  }
81912
82116
  });
81913
82117
 
81914
- // packages/analyzer/dist/rules/code-quality/visitors/javascript/commented-out-code.js
81915
- var commentedOutCodeVisitor;
82118
+ // packages/analyzer/dist/rules/code-quality/visitors/_shared/commented-out-code.js
82119
+ function stripLineMarker(line) {
82120
+ return line.replace(/^\s*(?:\/\/+|#+)\s?/, "").replace(/^\s*\*\/?\s?/, "").replace(/\*\/\s*$/, "").trim();
82121
+ }
82122
+ function looksLikeProse(line) {
82123
+ const words = line.match(/[A-Za-z][A-Za-z'-]+/g) ?? [];
82124
+ const codePunct = (line.match(/[;{}()=<>[\]]/g) ?? []).length;
82125
+ return words.length >= 4 && codePunct <= 1;
82126
+ }
82127
+ function looksLikeCode(line, opts) {
82128
+ if (/[;{}]\s*$/.test(line))
82129
+ return true;
82130
+ if (opts.colonTerminates && /:\s*$/.test(line))
82131
+ return true;
82132
+ if (/=>/.test(line))
82133
+ return true;
82134
+ if (/^[\w.]+\s*\([^)]*\)\s*;?\s*$/.test(line))
82135
+ return true;
82136
+ if (/^[\w.[\]]+\s*=\s*[^=].*$/.test(line) && !/==/.test(line))
82137
+ return true;
82138
+ return false;
82139
+ }
82140
+ function isCommentedOutCode(inner, opts) {
82141
+ let code = 0;
82142
+ let prose = 0;
82143
+ for (const raw of inner.split("\n")) {
82144
+ const line = stripLineMarker(raw);
82145
+ if (line.length === 0)
82146
+ continue;
82147
+ if (looksLikeProse(line))
82148
+ prose++;
82149
+ else if (looksLikeCode(line, opts))
82150
+ code++;
82151
+ }
82152
+ return code > 0 && code >= prose;
82153
+ }
82154
+ function lineCommentRunInner(node2, opts) {
82155
+ const isRunLine = (n) => n != null && n.type === "comment" && opts.isLineComment(n.text);
82156
+ if (!isRunLine(node2))
82157
+ return null;
82158
+ const prev = node2.previousSibling;
82159
+ if (isRunLine(prev) && prev.startPosition.row === node2.startPosition.row - 1)
82160
+ return null;
82161
+ const lines = [opts.strip(node2.text)];
82162
+ let cur = node2.nextSibling;
82163
+ let lastRow = node2.startPosition.row;
82164
+ while (isRunLine(cur) && cur.startPosition.row === lastRow + 1) {
82165
+ lines.push(opts.strip(cur.text));
82166
+ lastRow = cur.startPosition.row;
82167
+ cur = cur.nextSibling;
82168
+ }
82169
+ return lines.join("\n");
82170
+ }
81916
82171
  var init_commented_out_code = __esm({
82172
+ "packages/analyzer/dist/rules/code-quality/visitors/_shared/commented-out-code.js"() {
82173
+ "use strict";
82174
+ }
82175
+ });
82176
+
82177
+ // packages/analyzer/dist/rules/code-quality/visitors/javascript/commented-out-code.js
82178
+ var isLineComment, commentedOutCodeVisitor;
82179
+ var init_commented_out_code2 = __esm({
81917
82180
  "packages/analyzer/dist/rules/code-quality/visitors/javascript/commented-out-code.js"() {
81918
82181
  "use strict";
81919
82182
  init_types2();
82183
+ init_commented_out_code();
82184
+ isLineComment = (t2) => t2.startsWith("//");
81920
82185
  commentedOutCodeVisitor = {
81921
82186
  ruleKey: "code-quality/deterministic/commented-out-code",
81922
82187
  languages: ["typescript", "tsx", "javascript"],
@@ -81925,21 +82190,19 @@ var init_commented_out_code = __esm({
81925
82190
  const text4 = node2.text;
81926
82191
  if (text4.startsWith("/**"))
81927
82192
  return null;
81928
- let inner = text4;
81929
- if (inner.startsWith("//"))
81930
- inner = inner.slice(2).trim();
81931
- else if (inner.startsWith("/*"))
81932
- inner = inner.slice(2, -2).trim();
81933
- if (inner.length < 10)
81934
- return null;
81935
- const codePatterns = [
81936
- /^\s*(const|let|var|function|return|if|for|while|import|export|class|throw|try|catch)\s/,
81937
- /[;{}]\s*$/,
81938
- /=>/,
81939
- /\w+\(.*\)\s*[;{]?\s*$/
81940
- ];
81941
- const matchCount = codePatterns.filter((p2) => p2.test(inner)).length;
81942
- if (matchCount >= 2) {
82193
+ let inner;
82194
+ if (isLineComment(text4)) {
82195
+ inner = lineCommentRunInner(node2, { isLineComment, strip: (t2) => t2.replace(/^\/\/+/, "") });
82196
+ if (inner === null)
82197
+ return null;
82198
+ } else if (text4.startsWith("/*")) {
82199
+ inner = text4.slice(2, -2);
82200
+ } else {
82201
+ return null;
82202
+ }
82203
+ if (inner.trim().length < 10)
82204
+ return null;
82205
+ if (isCommentedOutCode(inner, {})) {
81943
82206
  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.");
81944
82207
  }
81945
82208
  return null;
@@ -82720,6 +82983,11 @@ var init_for_in_without_filter = __esm({
82720
82983
  return true;
82721
82984
  }
82722
82985
  }
82986
+ if (n.type === "member_expression") {
82987
+ const prop = n.childForFieldName("property");
82988
+ if (prop?.text === "hasOwnProperty" || prop?.text === "hasOwn")
82989
+ return true;
82990
+ }
82723
82991
  if (n.type === "string" && (n.text.includes("hasOwnProperty") || n.text.includes("hasOwn")))
82724
82992
  return true;
82725
82993
  for (let i = 0; i < n.childCount; i++) {
@@ -84087,6 +84355,10 @@ var init_selector_parameter = __esm({
84087
84355
  languages: ["typescript", "tsx", "javascript"],
84088
84356
  nodeTypes: JS_FUNCTION_TYPES,
84089
84357
  visit(node2, filePath, sourceCode) {
84358
+ const fnParent = node2.parent;
84359
+ if ((node2.type === "arrow_function" || node2.type === "function_expression") && (fnParent?.type === "arguments" || fnParent?.type === "jsx_expression")) {
84360
+ return null;
84361
+ }
84090
84362
  const params = node2.childForFieldName("parameters");
84091
84363
  if (!params)
84092
84364
  return null;
@@ -86169,6 +86441,15 @@ var init_meaningless_void_operator = __esm({
86169
86441
  });
86170
86442
 
86171
86443
  // packages/analyzer/dist/rules/code-quality/visitors/javascript/test-with-hardcoded-timeout.js
86444
+ function isTestFile(filePath) {
86445
+ const norm = filePath.replace(/\\/g, "/");
86446
+ const base2 = norm.slice(norm.lastIndexOf("/") + 1);
86447
+ if (/\.(test|spec)\.[cm]?[jt]sx?$/i.test(base2))
86448
+ return true;
86449
+ if (/(^|\/)__tests?__\//.test(norm))
86450
+ return true;
86451
+ return false;
86452
+ }
86172
86453
  var testWithHardcodedTimeoutVisitor;
86173
86454
  var init_test_with_hardcoded_timeout = __esm({
86174
86455
  "packages/analyzer/dist/rules/code-quality/visitors/javascript/test-with-hardcoded-timeout.js"() {
@@ -86179,7 +86460,7 @@ var init_test_with_hardcoded_timeout = __esm({
86179
86460
  languages: ["typescript", "tsx", "javascript"],
86180
86461
  nodeTypes: ["call_expression"],
86181
86462
  visit(node2, filePath, sourceCode) {
86182
- if (!filePath.includes("test") && !filePath.includes("spec") && !filePath.includes(".test.") && !filePath.includes(".spec."))
86463
+ if (!isTestFile(filePath))
86183
86464
  return null;
86184
86465
  const fn = node2.childForFieldName("function");
86185
86466
  if (!fn)
@@ -87539,8 +87820,8 @@ var init_test_empty_file = __esm({
87539
87820
  nodeTypes: ["program"],
87540
87821
  visit(node2, filePath, sourceCode) {
87541
87822
  const lowerPath = filePath.toLowerCase();
87542
- const isTestFile5 = lowerPath.includes(".test.") || lowerPath.includes(".spec.") || lowerPath.includes("__tests__");
87543
- if (!isTestFile5)
87823
+ const isTestFile6 = lowerPath.includes(".test.") || lowerPath.includes(".spec.") || lowerPath.includes("__tests__");
87824
+ if (!isTestFile6)
87544
87825
  return null;
87545
87826
  if (hasTestFunction(node2))
87546
87827
  return null;
@@ -91246,7 +91527,7 @@ var init_javascript5 = __esm({
91246
91527
  init_useless_computed_key();
91247
91528
  init_useless_concat();
91248
91529
  init_strict_equality();
91249
- init_commented_out_code();
91530
+ init_commented_out_code2();
91250
91531
  init_inverted_boolean();
91251
91532
  init_prefer_single_boolean_return();
91252
91533
  init_prefer_immediate_return();
@@ -92413,30 +92694,27 @@ var init_unused_variable2 = __esm({
92413
92694
  });
92414
92695
 
92415
92696
  // packages/analyzer/dist/rules/code-quality/visitors/python/commented-out-code.js
92416
- var pythonCommentedOutCodeVisitor;
92417
- var init_commented_out_code2 = __esm({
92697
+ var isLineComment2, pythonCommentedOutCodeVisitor;
92698
+ var init_commented_out_code3 = __esm({
92418
92699
  "packages/analyzer/dist/rules/code-quality/visitors/python/commented-out-code.js"() {
92419
92700
  "use strict";
92420
92701
  init_types2();
92702
+ init_commented_out_code();
92703
+ isLineComment2 = (t2) => t2.startsWith("#");
92421
92704
  pythonCommentedOutCodeVisitor = {
92422
92705
  ruleKey: "code-quality/deterministic/commented-out-code",
92423
92706
  languages: ["python"],
92424
92707
  nodeTypes: ["comment"],
92425
92708
  visit(node2, filePath, sourceCode) {
92426
92709
  const text4 = node2.text;
92427
- if (!text4.startsWith("#"))
92710
+ if (!isLineComment2(text4))
92428
92711
  return null;
92429
- const inner = text4.slice(1).trim();
92430
- if (inner.length < 10)
92712
+ const inner = lineCommentRunInner(node2, { isLineComment: isLineComment2, strip: (t2) => t2.replace(/^#+/, "") });
92713
+ if (inner === null)
92431
92714
  return null;
92432
- const codePatterns = [
92433
- /\b(def|class|import|from|return|if|for|while|try|except|with|raise|print)\b/,
92434
- /[;:]\s*$/,
92435
- /\w+\(.*\)/,
92436
- /=\s*\w/
92437
- ];
92438
- const matchCount = codePatterns.filter((p2) => p2.test(inner)).length;
92439
- if (matchCount >= 2) {
92715
+ if (inner.trim().length < 10)
92716
+ return null;
92717
+ if (isCommentedOutCode(inner, { colonTerminates: true })) {
92440
92718
  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.");
92441
92719
  }
92442
92720
  return null;
@@ -101429,8 +101707,8 @@ var init_test_not_discoverable = __esm({
101429
101707
  return null;
101430
101708
  const className = classNameNode.text;
101431
101709
  const isTestClass = className.startsWith("Test") || className.endsWith("Test") || className.endsWith("Tests");
101432
- const isTestFile5 = isPythonTestFile(filePath);
101433
- if (!isTestClass && !isTestFile5)
101710
+ const isTestFile6 = isPythonTestFile(filePath);
101711
+ if (!isTestClass && !isTestFile6)
101434
101712
  return null;
101435
101713
  const body = node2.childForFieldName("body");
101436
101714
  if (!body)
@@ -103199,7 +103477,7 @@ var init_python7 = __esm({
103199
103477
  init_no_debugger2();
103200
103478
  init_require_await2();
103201
103479
  init_unused_variable2();
103202
- init_commented_out_code2();
103480
+ init_commented_out_code3();
103203
103481
  init_abstract_class_without_abstract_method();
103204
103482
  init_and_or_ternary();
103205
103483
  init_assert_in_production();
@@ -105147,7 +105425,12 @@ var init_result = __esm({
105147
105425
  durationMs: external_exports.number().nonnegative(),
105148
105426
  /** Present on `fail` / `error`. */
105149
105427
  failure: GuardFailureDetailSchema.optional(),
105150
- /** Repo-relative pointer into `evidence/`; present on `fail` / `error`. */
105428
+ /**
105429
+ * Repo-relative pointer into `evidence/`; present on every EXECUTED outcome —
105430
+ * `pass` (the run transcript proving what ran) as well as `fail` / `error`.
105431
+ * Absent on non-executed `stale` / `orphaned`, and on pre-2026-07-08 runs whose
105432
+ * passes were never given a bundle (optional keeps those parsing + rendering).
105433
+ */
105151
105434
  evidencePath: external_exports.string().optional(),
105152
105435
  /**
105153
105436
  * Present when the bound section moved: the section kept its text but now
@@ -105725,7 +106008,18 @@ function withLanguageSupport(rules, visitors) {
105725
106008
  continue;
105726
106009
  }
105727
106010
  if (rule.engine === "roslyn-host" || rule.engine === "roslyn-workspace") {
105728
- support[language] = language === "csharp" ? { status: "supported" } : { status: "not-applicable", reason: "C# semantic rule (Roslyn host); no equivalent in this language" };
106011
+ if (language === "csharp") {
106012
+ support[language] = { status: "supported" };
106013
+ continue;
106014
+ }
106015
+ const hasExplicitForLang = explicitFamilies.get(rule.key)?.has(language) ?? false;
106016
+ const universalExcludesLang2 = universalExcludedFamilies.get(rule.key)?.has(language) ?? false;
106017
+ const hasUniversalForLang = universalRuleKeys.has(rule.key) && UNIVERSAL_VISITOR_FAMILIES.includes(language) && !universalExcludesLang2;
106018
+ if (hasExplicitForLang || hasUniversalForLang) {
106019
+ support[language] = { status: "supported" };
106020
+ continue;
106021
+ }
106022
+ support[language] = { status: "not-applicable", reason: "C# semantic rule (Roslyn host); no equivalent in this language" };
105729
106023
  continue;
105730
106024
  }
105731
106025
  const hasExplicit = explicitFamilies.get(rule.key)?.has(language) ?? false;
@@ -114484,6 +114778,12 @@ function walkCSharp(node2, visit) {
114484
114778
  walkCSharp(child, visit);
114485
114779
  }
114486
114780
  }
114781
+ function isCSharpTestPath(filePath) {
114782
+ return filePath.split(/[\\/]/).some((segment) => {
114783
+ const s = segment.toLowerCase();
114784
+ return /\.tests?$/.test(s) || s.endsWith(".testbase") || s.endsWith("testapp");
114785
+ });
114786
+ }
114487
114787
  var FUNCTION_BOUNDARY_TYPES;
114488
114788
  var init_csharp_helpers = __esm({
114489
114789
  "packages/analyzer/dist/rules/_shared/csharp-helpers.js"() {
@@ -115898,7 +116198,7 @@ function isTypeConstantAccess(node2) {
115898
116198
  const receiver = lastSegment(node2.childForFieldName("expression")?.text ?? "");
115899
116199
  return /^[A-Z]/.test(receiver);
115900
116200
  }
115901
- function leafName(node2) {
116201
+ function leafName2(node2) {
115902
116202
  if (node2.type === "identifier")
115903
116203
  return node2.text;
115904
116204
  if (node2.type === "member_access_expression")
@@ -115934,8 +116234,8 @@ var init_timing_attack_comparison2 = __esm({
115934
116234
  return null;
115935
116235
  if (isTypeConstantAccess(left) || isTypeConstantAccess(right))
115936
116236
  return null;
115937
- const leftLeaf = leafName(left);
115938
- const rightLeaf = leafName(right);
116237
+ const leftLeaf = leafName2(left);
116238
+ const rightLeaf = leafName2(right);
115939
116239
  if (leftLeaf === "Length" || rightLeaf === "Length")
115940
116240
  return null;
115941
116241
  if (ENUM_CONSTANT_PATTERN2.test(leftLeaf) || ENUM_CONSTANT_PATTERN2.test(rightLeaf))
@@ -115954,7 +116254,7 @@ var init_timing_attack_comparison2 = __esm({
115954
116254
  });
115955
116255
 
115956
116256
  // packages/analyzer/dist/rules/security/visitors/csharp/insecure-random.js
115957
- function isTestFile(filePath) {
116257
+ function isTestFile2(filePath) {
115958
116258
  const fileName = filePath.replace(/^.*[\\/]/, "");
115959
116259
  return /Tests?\.cs$/i.test(fileName) || /(?:TestSetup|TestFixture|Fixtures?|FunctionalTests|IntegrationTests|UnitTests|TestHelpers?)/.test(fileName);
115960
116260
  }
@@ -115996,7 +116296,7 @@ var init_insecure_random3 = __esm({
115996
116296
  languages: ["csharp"],
115997
116297
  nodeTypes: ["object_creation_expression", "member_access_expression"],
115998
116298
  visit(node2, filePath, sourceCode) {
115999
- if (isTestFile(filePath))
116299
+ if (isTestFile2(filePath))
116000
116300
  return null;
116001
116301
  if (node2.type === "object_creation_expression") {
116002
116302
  if (getCreatedTypeName(node2) !== "Random")
@@ -118467,11 +118767,14 @@ var init_empty_catch3 = __esm({
118467
118767
  const stmt = tryStatements[0];
118468
118768
  if (/\b(Parse|TryParse|Deserialize)\s*[<(]/.test(stmt.text))
118469
118769
  return null;
118470
- const expr = stmt.namedChildren[0];
118471
- if (expr?.type === "invocation_expression" && BEST_EFFORT_METHODS.has(getCSharpMethodName(expr))) {
118770
+ let expr = stmt.namedChildren[0];
118771
+ if (expr?.type === "await_expression")
118772
+ expr = expr.namedChildren[0];
118773
+ const isBestEffort = (name) => BEST_EFFORT_METHODS.has(name.replace(/Async$/, ""));
118774
+ if (expr?.type === "invocation_expression" && isBestEffort(getCSharpMethodName(expr))) {
118472
118775
  return null;
118473
118776
  }
118474
- if (expr?.type === "conditional_access_expression" && BEST_EFFORT_METHODS.has((expr.text.match(/\.(\w+)\s*\([^()]*\)\s*$/) ?? [])[1] ?? "")) {
118777
+ if (expr?.type === "conditional_access_expression" && isBestEffort((expr.text.match(/\.(\w+)\s*\([^()]*\)\s*$/) ?? [])[1] ?? "")) {
118475
118778
  return null;
118476
118779
  }
118477
118780
  }
@@ -118535,6 +118838,8 @@ var init_self_assignment3 = __esm({
118535
118838
  languages: ["csharp"],
118536
118839
  nodeTypes: ["assignment_expression"],
118537
118840
  visit(node2, filePath, sourceCode) {
118841
+ if (node2.parent?.type === "initializer_expression")
118842
+ return null;
118538
118843
  const left = node2.childForFieldName("left");
118539
118844
  const right = node2.childForFieldName("right");
118540
118845
  const operator = node2.childForFieldName("operator");
@@ -119542,7 +119847,7 @@ var init_infinite_recursion2 = __esm({
119542
119847
  return null;
119543
119848
  if (node2.type === "property_declaration") {
119544
119849
  const arrow = node2.childForFieldName("value");
119545
- const arrowExpr = arrow?.namedChildren[0];
119850
+ const arrowExpr = arrow?.type === "arrow_expression_clause" ? arrow.namedChildren[0] : null;
119546
119851
  if (arrowExpr?.type === "identifier" && arrowExpr.text === name) {
119547
119852
  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.");
119548
119853
  }
@@ -121607,8 +121912,11 @@ var init_useless_finally2 = __esm({
121607
121912
  });
121608
121913
 
121609
121914
  // packages/analyzer/dist/rules/bugs/visitors/csharp/fstring-missing-placeholders.js
121610
- function hasInterpolation(node2) {
121611
- return node2.namedChildren.some((c2) => c2?.type === "interpolation");
121915
+ function hasRealHole(node2) {
121916
+ if (node2.namedChildren.some((c2) => c2?.type === "interpolation"))
121917
+ return true;
121918
+ const stripped = node2.text.replace(/\{\{/g, "").replace(/\}\}/g, "");
121919
+ return stripped.includes("{");
121612
121920
  }
121613
121921
  var csharpFstringMissingPlaceholdersVisitor;
121614
121922
  var init_fstring_missing_placeholders2 = __esm({
@@ -121620,7 +121928,7 @@ var init_fstring_missing_placeholders2 = __esm({
121620
121928
  languages: ["csharp"],
121621
121929
  nodeTypes: ["interpolated_string_expression"],
121622
121930
  visit(node2, filePath, sourceCode) {
121623
- if (hasInterpolation(node2))
121931
+ if (hasRealHole(node2))
121624
121932
  return null;
121625
121933
  let top = node2;
121626
121934
  while (top.parent?.type === "binary_expression" && top.parent.childForFieldName("operator")?.text === "+") {
@@ -121631,7 +121939,7 @@ var init_fstring_missing_placeholders2 = __esm({
121631
121939
  const walk20 = (n) => {
121632
121940
  if (siblingHasHoles)
121633
121941
  return;
121634
- if (n.type === "interpolated_string_expression" && n.id !== node2.id && hasInterpolation(n)) {
121942
+ if (n.type === "interpolated_string_expression" && n.id !== node2.id && hasRealHole(n)) {
121635
121943
  siblingHasHoles = true;
121636
121944
  return;
121637
121945
  }
@@ -123111,6 +123419,32 @@ var init_chained_orderby_loses_ordering = __esm({
123111
123419
  });
123112
123420
 
123113
123421
  // packages/analyzer/dist/rules/bugs/visitors/csharp/check-against-value-being-assigned.js
123422
+ function enclosingTypeDecl(node2) {
123423
+ let p2 = node2.parent;
123424
+ while (p2) {
123425
+ if (p2.type === "class_declaration" || p2.type === "struct_declaration" || p2.type === "record_declaration") {
123426
+ return p2;
123427
+ }
123428
+ p2 = p2.parent;
123429
+ }
123430
+ return null;
123431
+ }
123432
+ function selfMemberName(target) {
123433
+ if (target.type === "identifier")
123434
+ return target.text;
123435
+ if (target.type === "member_access_expression") {
123436
+ const obj = target.childForFieldName("expression");
123437
+ if (obj?.type === "this")
123438
+ return target.childForFieldName("name")?.text ?? null;
123439
+ }
123440
+ return null;
123441
+ }
123442
+ function enclosingTypeHasProperty(typeDecl, name) {
123443
+ const body = typeDecl.childForFieldName("body") ?? typeDecl.namedChildren.find((c2) => c2?.type === "declaration_list");
123444
+ if (!body)
123445
+ return false;
123446
+ return body.namedChildren.some((m) => m?.type === "property_declaration" && m.childForFieldName("name")?.text === name);
123447
+ }
123114
123448
  function singleStatement(consequence) {
123115
123449
  if (consequence.type === "block") {
123116
123450
  const stmts = consequence.namedChildren.filter((c2) => c2?.type !== "comment");
@@ -123158,6 +123492,12 @@ var init_check_against_value_being_assigned = __esm({
123158
123492
  const sameBA = condLeft.text === value.text && condRight.text === target.text;
123159
123493
  if (!sameAB && !sameBA)
123160
123494
  return null;
123495
+ const selfName = selfMemberName(target);
123496
+ if (selfName) {
123497
+ const typeDecl = enclosingTypeDecl(node2);
123498
+ if (typeDecl && enclosingTypeHasProperty(typeDecl, selfName))
123499
+ return null;
123500
+ }
123161
123501
  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.");
123162
123502
  }
123163
123503
  };
@@ -123647,7 +123987,8 @@ var init_redundant_base_call = __esm({
123647
123987
  const receiver = node2.childForFieldName("expression");
123648
123988
  if (receiver?.type !== "base" && receiver?.text !== "base")
123649
123989
  return null;
123650
- const memberName3 = node2.childForFieldName("name")?.text;
123990
+ const nameNode = node2.childForFieldName("name");
123991
+ const memberName3 = nameNode?.type === "generic_name" ? nameNode.namedChildren.find((c2) => c2?.type === "identifier")?.text : nameNode?.text;
123651
123992
  if (!memberName3)
123652
123993
  return null;
123653
123994
  const typeDecl = enclosingType(node2);
@@ -123982,6 +124323,19 @@ function baseEntryName(entry) {
123982
124323
  }
123983
124324
  return null;
123984
124325
  }
124326
+ function baseEntryArity(entry) {
124327
+ const generic = entry.type === "generic_name" ? entry : entry.type === "qualified_name" ? entry.childForFieldName("name") : null;
124328
+ if (generic?.type !== "generic_name")
124329
+ return 0;
124330
+ const args = generic.namedChildren.find((c2) => c2?.type === "type_argument_list");
124331
+ return args ? args.namedChildren.filter((c2) => c2 != null).length : 0;
124332
+ }
124333
+ function declaredArity(node2) {
124334
+ const params = node2.childForFieldName("type_parameters");
124335
+ if (!params)
124336
+ return 0;
124337
+ return params.namedChildren.filter((c2) => c2?.type === "type_parameter").length;
124338
+ }
123985
124339
  var csharpRecursiveTypeInheritanceVisitor;
123986
124340
  var init_recursive_type_inheritance = __esm({
123987
124341
  "packages/analyzer/dist/rules/bugs/visitors/csharp/recursive-type-inheritance.js"() {
@@ -124003,6 +124357,8 @@ var init_recursive_type_inheritance = __esm({
124003
124357
  return null;
124004
124358
  if (baseEntryName(firstBase) !== typeName2)
124005
124359
  return null;
124360
+ if (baseEntryArity(firstBase) !== declaredArity(node2))
124361
+ return null;
124006
124362
  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.");
124007
124363
  }
124008
124364
  };
@@ -126230,6 +126586,20 @@ function isCSharpGeneratedSource(filePath, sourceCode) {
126230
126586
  return true;
126231
126587
  return sourceCode.slice(0, 600).includes("<auto-generated");
126232
126588
  }
126589
+ function isCSharpEfMigration(node2) {
126590
+ const cls = getCSharpEnclosingClass(node2);
126591
+ if (!cls)
126592
+ return false;
126593
+ const baseList = cls.namedChildren.find((c2) => c2?.type === "base_list");
126594
+ if (!baseList)
126595
+ return false;
126596
+ return baseList.namedChildren.some((base2) => {
126597
+ if (!base2)
126598
+ return false;
126599
+ const simple = base2.text.split(".").pop() ?? base2.text;
126600
+ return simple === "Migration";
126601
+ });
126602
+ }
126233
126603
  function isCSharpPrivateMember(node2) {
126234
126604
  const accessibility = [];
126235
126605
  for (const child of node2.children) {
@@ -128793,17 +129163,13 @@ var init_collapsible_if3 = __esm({
128793
129163
  });
128794
129164
 
128795
129165
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/commented-out-code.js
128796
- var CODE_PATTERNS, csharpCommentedOutCodeVisitor;
128797
- var init_commented_out_code3 = __esm({
129166
+ var isLineComment3, csharpCommentedOutCodeVisitor;
129167
+ var init_commented_out_code4 = __esm({
128798
129168
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/commented-out-code.js"() {
128799
129169
  "use strict";
128800
129170
  init_types2();
128801
- CODE_PATTERNS = [
128802
- /^\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/,
128803
- /[;{}]\s*$/,
128804
- /=>/,
128805
- /\w+\(.*\)\s*[;{]?\s*$/
128806
- ];
129171
+ init_commented_out_code();
129172
+ isLineComment3 = (t2) => t2.startsWith("//") && !t2.startsWith("///");
128807
129173
  csharpCommentedOutCodeVisitor = {
128808
129174
  ruleKey: "code-quality/deterministic/commented-out-code",
128809
129175
  languages: ["csharp"],
@@ -128812,15 +129178,19 @@ var init_commented_out_code3 = __esm({
128812
129178
  const text4 = node2.text;
128813
129179
  if (text4.startsWith("///") || text4.startsWith("/**"))
128814
129180
  return null;
128815
- let inner = text4;
128816
- if (inner.startsWith("//"))
128817
- inner = inner.slice(2).trim();
128818
- else if (inner.startsWith("/*"))
128819
- inner = inner.slice(2, -2).trim();
128820
- if (inner.length < 10)
129181
+ let inner;
129182
+ if (isLineComment3(text4)) {
129183
+ inner = lineCommentRunInner(node2, { isLineComment: isLineComment3, strip: (t2) => t2.replace(/^\/\/+/, "") });
129184
+ if (inner === null)
129185
+ return null;
129186
+ } else if (text4.startsWith("/*")) {
129187
+ inner = text4.slice(2, -2);
129188
+ } else {
128821
129189
  return null;
128822
- const matchCount = CODE_PATTERNS.filter((p2) => p2.test(inner)).length;
128823
- if (matchCount >= 2) {
129190
+ }
129191
+ if (inner.trim().length < 10)
129192
+ return null;
129193
+ if (isCommentedOutCode(inner, {})) {
128824
129194
  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.");
128825
129195
  }
128826
129196
  return null;
@@ -129528,6 +129898,13 @@ function csharpEmptyBodyIsIntentional(node2, body) {
129528
129898
  }
129529
129899
  if (isCSharpNoOpCallbackPosition(node2))
129530
129900
  return true;
129901
+ let enclosingType3 = node2.parent;
129902
+ while (enclosingType3 && enclosingType3.type !== "class_declaration" && enclosingType3.type !== "struct_declaration" && enclosingType3.type !== "record_declaration" && enclosingType3.type !== "record_struct_declaration") {
129903
+ enclosingType3 = enclosingType3.parent;
129904
+ }
129905
+ const typeName2 = enclosingType3?.childForFieldName("name")?.text;
129906
+ if (typeName2 && /^(Null|Noop|NoOp|Empty)[A-Z0-9]/.test(typeName2))
129907
+ return true;
129531
129908
  if (node2.type === "method_declaration") {
129532
129909
  if (hasCSharpModifier(node2, "virtual") || hasCSharpModifier(node2, "override") || hasCSharpModifier(node2, "partial"))
129533
129910
  return true;
@@ -129805,6 +130182,7 @@ var init_hardcoded_url2 = __esm({
129805
130182
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/hardcoded-url.js"() {
129806
130183
  "use strict";
129807
130184
  init_types2();
130185
+ init_csharp_helpers();
129808
130186
  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/;
129809
130187
  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/;
129810
130188
  PLACEHOLDER_NAME = /default|placeholder|example|sample|template|fallback|demo|test|mock|stub|dummy/;
@@ -129816,6 +130194,8 @@ var init_hardcoded_url2 = __esm({
129816
130194
  const text4 = node2.text;
129817
130195
  if (!/https?:\/\/[a-zA-Z0-9]/.test(text4))
129818
130196
  return null;
130197
+ if (isCSharpTestPath(filePath))
130198
+ return null;
129819
130199
  if (text4.includes("example.com") || text4.includes("localhost") || text4.includes("127.0.0.1") || text4.includes("placeholder"))
129820
130200
  return null;
129821
130201
  if (NAMESPACE_URI_HOSTS.test(text4))
@@ -129836,6 +130216,42 @@ var init_hardcoded_url2 = __esm({
129836
130216
  });
129837
130217
 
129838
130218
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/identical-functions.js
130219
+ function isTrivialExpr(e) {
130220
+ switch (e.type) {
130221
+ case "integer_literal":
130222
+ case "real_literal":
130223
+ case "boolean_literal":
130224
+ case "string_literal":
130225
+ case "verbatim_string_literal":
130226
+ case "null_literal":
130227
+ case "character_literal":
130228
+ case "identifier":
130229
+ case "this_expression":
130230
+ case "default_expression":
130231
+ case "member_access_expression":
130232
+ return true;
130233
+ case "invocation_expression": {
130234
+ const args = e.childForFieldName("arguments");
130235
+ if (!args)
130236
+ return true;
130237
+ return args.namedChildren.every((a) => {
130238
+ if (a?.type !== "argument")
130239
+ return true;
130240
+ const value = a.namedChildren.find((c2) => c2 && c2.type !== "name_colon");
130241
+ return !value || isTrivialExpr(value);
130242
+ });
130243
+ }
130244
+ default:
130245
+ return false;
130246
+ }
130247
+ }
130248
+ function isTrivialReturnBody(body) {
130249
+ const stmts = body.namedChildren.filter((c2) => c2 && c2.type !== "comment");
130250
+ if (stmts.length !== 1 || stmts[0]?.type !== "return_statement")
130251
+ return false;
130252
+ const expr = stmts[0].namedChildren.find((c2) => c2 && c2.type !== "comment");
130253
+ return !expr || isTrivialExpr(expr);
130254
+ }
129839
130255
  var COMPARED_TYPES, csharpIdenticalFunctionsVisitor;
129840
130256
  var init_identical_functions2 = __esm({
129841
130257
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/identical-functions.js"() {
@@ -129854,7 +130270,7 @@ var init_identical_functions2 = __esm({
129854
130270
  const body = n.childForFieldName("body");
129855
130271
  if (body && body.type === "block" && body.namedChildCount > 0) {
129856
130272
  const onlyThrow = body.namedChildCount === 1 && body.namedChildren[0]?.type === "throw_statement";
129857
- if (!onlyThrow) {
130273
+ if (!onlyThrow && !isTrivialReturnBody(body)) {
129858
130274
  const normalized = body.text.replace(/\s+/g, " ").trim();
129859
130275
  bodies.push({ body: normalized, fnNode: n });
129860
130276
  }
@@ -130044,6 +130460,8 @@ var init_magic_number2 = __esm({
130044
130460
  return null;
130045
130461
  if (parentType === "argument" && parent.parent?.type === "bracketed_argument_list")
130046
130462
  return null;
130463
+ if (parentType === "argument" && parent.childForFieldName("name"))
130464
+ return null;
130047
130465
  if (parentType === "binary_expression" && TIME_FACTORS2.has(val)) {
130048
130466
  const op = parent.childForFieldName("operator");
130049
130467
  if (op?.text === "*") {
@@ -130365,6 +130783,8 @@ var init_no_extraneous_class2 = __esm({
130365
130783
  return null;
130366
130784
  if (node2.namedChildren.some((c2) => c2?.type === "base_list"))
130367
130785
  return null;
130786
+ if (node2.namedChildren.some((c2) => c2?.type === "attribute_list"))
130787
+ return null;
130368
130788
  const body = node2.childForFieldName("body");
130369
130789
  if (!body)
130370
130790
  return null;
@@ -130623,7 +131043,8 @@ var init_redundant_jump3 = __esm({
130623
131043
  if (!grandparent)
130624
131044
  return null;
130625
131045
  if (node2.type === "return_statement") {
130626
- if (node2.namedChildren.length > 0)
131046
+ const hasExpression = node2.children.some((c2) => c2 != null && c2.type !== "return" && c2.type !== ";");
131047
+ if (hasExpression)
130627
131048
  return null;
130628
131049
  if (!isCSharpFunctionBoundary(grandparent.type))
130629
131050
  return null;
@@ -131988,6 +132409,8 @@ var init_too_many_lines3 = __esm({
131988
132409
  const bodyNode = getCSharpFunctionBody(node2);
131989
132410
  if (!bodyNode)
131990
132411
  return null;
132412
+ if (isCSharpGeneratedSource(filePath, sourceCode) || isCSharpEfMigration(node2))
132413
+ return null;
131991
132414
  const lineCount = bodyNode.endPosition.row - bodyNode.startPosition.row + 1;
131992
132415
  if (lineCount > 50) {
131993
132416
  const name = getCSharpFunctionName(node2);
@@ -132635,93 +133058,6 @@ var init_unused_collection2 = __esm({
132635
133058
  }
132636
133059
  });
132637
133060
 
132638
- // packages/analyzer/dist/rules/code-quality/visitors/csharp/unused-function-parameter.js
132639
- var csharpUnusedFunctionParameterVisitor;
132640
- var init_unused_function_parameter2 = __esm({
132641
- "packages/analyzer/dist/rules/code-quality/visitors/csharp/unused-function-parameter.js"() {
132642
- "use strict";
132643
- init_types2();
132644
- init_csharp_helpers();
132645
- csharpUnusedFunctionParameterVisitor = {
132646
- ruleKey: "code-quality/deterministic/unused-function-parameter",
132647
- languages: ["csharp"],
132648
- nodeTypes: ["method_declaration", "constructor_declaration", "local_function_statement"],
132649
- visit(node2, filePath, sourceCode) {
132650
- const params = node2.childForFieldName("parameters");
132651
- if (!params || params.namedChildCount === 0)
132652
- return null;
132653
- const body = node2.childForFieldName("body");
132654
- if (!body)
132655
- return null;
132656
- if (body.type === "block" && body.namedChildCount === 0)
132657
- return null;
132658
- if (/NotImplementedException|NotSupportedException/.test(body.text))
132659
- return null;
132660
- if (body.type === "block" && body.namedChildCount === 1 && body.namedChildren[0]?.type === "return_statement" && /Task\.CompletedTask|Task\.FromResult|ValueTask\.CompletedTask/.test(body.namedChildren[0].text)) {
132661
- return null;
132662
- }
132663
- if (node2.type === "method_declaration") {
132664
- for (const mod of ["override", "virtual", "abstract", "partial", "extern"]) {
132665
- if (hasCSharpModifier(node2, mod))
132666
- return null;
132667
- }
132668
- if (node2.namedChildren.some((c2) => c2?.type === "explicit_interface_specifier"))
132669
- return null;
132670
- if (node2.childForFieldName("name")?.text === "Main")
132671
- return null;
132672
- }
132673
- const paramList = [];
132674
- for (const param of params.namedChildren) {
132675
- if (!param || param.type !== "parameter")
132676
- continue;
132677
- if (param.children.some((c2) => c2?.type === "this" || c2?.type === "modifier" && c2.text === "this"))
132678
- continue;
132679
- if (param.childForFieldName("type")?.text === "CancellationToken")
132680
- continue;
132681
- const nameNode = param.childForFieldName("name");
132682
- if (nameNode?.type === "identifier")
132683
- paramList.push({ name: nameNode.text, node: param });
132684
- }
132685
- if (paramList.length === 0)
132686
- return null;
132687
- if (paramList.length === 2) {
132688
- const first2 = params.namedChildren.find((c2) => c2?.type === "parameter");
132689
- const firstType = first2?.childForFieldName("type")?.text;
132690
- const secondType = paramList[1] ? paramList[1].node.childForFieldName("type")?.text : void 0;
132691
- if ((firstType === "object" || firstType === "object?") && secondType?.endsWith("EventArgs"))
132692
- return null;
132693
- }
132694
- const used = /* @__PURE__ */ new Set();
132695
- function collectReads(n) {
132696
- if (n.type === "identifier") {
132697
- const parent = n.parent;
132698
- const isMemberName = parent && (parent.type === "member_access_expression" || parent.type === "qualified_name" || parent.type === "member_binding_expression") && parent.childForFieldName("name")?.id === n.id;
132699
- if (!isMemberName)
132700
- used.add(n.text);
132701
- }
132702
- for (let i = 0; i < n.namedChildCount; i++) {
132703
- const child = n.namedChild(i);
132704
- if (child)
132705
- collectReads(child);
132706
- }
132707
- }
132708
- collectReads(body);
132709
- const initializer = node2.namedChildren.find((c2) => c2?.type === "constructor_initializer");
132710
- if (initializer)
132711
- collectReads(initializer);
132712
- for (const { name, node: paramNode } of paramList) {
132713
- if (name.startsWith("_"))
132714
- continue;
132715
- if (!used.has(name)) {
132716
- 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}\`.`);
132717
- }
132718
- }
132719
- return null;
132720
- }
132721
- };
132722
- }
132723
- });
132724
-
132725
133061
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/unused-private-member.js
132726
133062
  var csharpUnusedPrivateMemberVisitor;
132727
133063
  var init_unused_private_member2 = __esm({
@@ -132746,6 +133082,8 @@ var init_unused_private_member2 = __esm({
132746
133082
  for (const member of body.namedChildren) {
132747
133083
  if (!member)
132748
133084
  continue;
133085
+ if (member.namedChildren.some((c2) => c2?.type === "explicit_interface_specifier"))
133086
+ continue;
132749
133087
  if (!isCSharpPrivateMember(member))
132750
133088
  continue;
132751
133089
  if (getCSharpDeclAttributeNames(member).length > 0)
@@ -133143,8 +133481,17 @@ var init_abstract_class_public_constructor = __esm({
133143
133481
  });
133144
133482
 
133145
133483
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/abstract-class-without-abstract-members.js
133146
- function hasAbstractMember(body) {
133147
- return body.namedChildren.some((c2) => c2 && MEMBER_TYPES2.has(c2.type) && hasCSharpModifier(c2, "abstract"));
133484
+ function hasMemberWithModifier(body, modifier) {
133485
+ return body.namedChildren.some((c2) => c2 != null && MEMBER_TYPES2.has(c2.type) && hasCSharpModifier(c2, modifier));
133486
+ }
133487
+ function hasProtectedConstructor(body) {
133488
+ return body.namedChildren.some((c2) => c2?.type === "constructor_declaration" && hasCSharpModifier(c2, "protected"));
133489
+ }
133490
+ function hasBaseList(classDecl) {
133491
+ return classDecl.namedChildren.some((c2) => c2?.type === "base_list");
133492
+ }
133493
+ function hasExtensionSignal(classDecl, body) {
133494
+ return hasMemberWithModifier(body, "abstract") || hasMemberWithModifier(body, "virtual") || hasMemberWithModifier(body, "override") || hasProtectedConstructor(body) || hasBaseList(classDecl);
133148
133495
  }
133149
133496
  var MEMBER_TYPES2, csharpAbstractClassWithoutAbstractMembersVisitor;
133150
133497
  var init_abstract_class_without_abstract_members = __esm({
@@ -133156,6 +133503,7 @@ var init_abstract_class_without_abstract_members = __esm({
133156
133503
  "method_declaration",
133157
133504
  "property_declaration",
133158
133505
  "event_declaration",
133506
+ "event_field_declaration",
133159
133507
  "indexer_declaration"
133160
133508
  ]);
133161
133509
  csharpAbstractClassWithoutAbstractMembersVisitor = {
@@ -133170,10 +133518,10 @@ var init_abstract_class_without_abstract_members = __esm({
133170
133518
  const body = node2.childForFieldName("body");
133171
133519
  if (!body)
133172
133520
  return null;
133173
- if (hasAbstractMember(body))
133521
+ if (hasExtensionSignal(node2, body))
133174
133522
  return null;
133175
133523
  const name = node2.childForFieldName("name")?.text ?? "class";
133176
- 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.");
133524
+ 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.");
133177
133525
  }
133178
133526
  };
133179
133527
  }
@@ -134091,6 +134439,8 @@ var init_static_holder_type_has_constructor = __esm({
134091
134439
  return null;
134092
134440
  if (hasCSharpModifier(node2, "abstract"))
134093
134441
  return null;
134442
+ if (node2.namedChildren.some((c2) => c2?.type === "base_list"))
134443
+ return null;
134094
134444
  const body = node2.namedChildren.find((c2) => c2?.type === "declaration_list");
134095
134445
  if (!body)
134096
134446
  return null;
@@ -135974,6 +136324,15 @@ var init_extension_method_on_object = __esm({
135974
136324
  });
135975
136325
 
135976
136326
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/generic-parameter-not-inferable.js
136327
+ function isInterfaceMember(method) {
136328
+ let current = method.parent;
136329
+ while (current) {
136330
+ if (TYPE_DECL_TYPES2.has(current.type))
136331
+ return current.type === "interface_declaration";
136332
+ current = current.parent;
136333
+ }
136334
+ return false;
136335
+ }
135977
136336
  function identifiersIn2(node2) {
135978
136337
  const used = /* @__PURE__ */ new Set();
135979
136338
  if (!node2)
@@ -135995,14 +136354,23 @@ function parameterTypeIdentifiers(method) {
135995
136354
  for (const id of identifiersIn2(param.childForFieldName("type")))
135996
136355
  used.add(id);
135997
136356
  }
136357
+ for (const id of identifiersIn2(params.childForFieldName("type")))
136358
+ used.add(id);
135998
136359
  return used;
135999
136360
  }
136000
- var csharpGenericParameterNotInferableVisitor;
136361
+ var TYPE_DECL_TYPES2, csharpGenericParameterNotInferableVisitor;
136001
136362
  var init_generic_parameter_not_inferable = __esm({
136002
136363
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/generic-parameter-not-inferable.js"() {
136003
136364
  "use strict";
136004
136365
  init_types2();
136005
136366
  init_csharp_helpers();
136367
+ TYPE_DECL_TYPES2 = /* @__PURE__ */ new Set([
136368
+ "interface_declaration",
136369
+ "class_declaration",
136370
+ "struct_declaration",
136371
+ "record_declaration",
136372
+ "record_struct_declaration"
136373
+ ]);
136006
136374
  csharpGenericParameterNotInferableVisitor = {
136007
136375
  ruleKey: "code-quality/deterministic/generic-parameter-not-inferable",
136008
136376
  languages: ["csharp"],
@@ -136011,6 +136379,8 @@ var init_generic_parameter_not_inferable = __esm({
136011
136379
  const typeParams = node2.namedChildren.find((c2) => c2?.type === "type_parameter_list");
136012
136380
  if (!typeParams)
136013
136381
  return null;
136382
+ if (isInterfaceMember(node2))
136383
+ return null;
136014
136384
  const usedInParams = parameterTypeIdentifiers(node2);
136015
136385
  const nameNode = node2.childForFieldName("name");
136016
136386
  const usedInReturn = /* @__PURE__ */ new Set();
@@ -136022,15 +136392,13 @@ var init_generic_parameter_not_inferable = __esm({
136022
136392
  for (const id of identifiersIn2(child))
136023
136393
  usedInReturn.add(id);
136024
136394
  }
136025
- const usedInBody = identifiersIn2(node2.childForFieldName("body"));
136026
136395
  for (const tp of typeParams.namedChildren) {
136027
136396
  if (tp?.type !== "type_parameter")
136028
136397
  continue;
136029
136398
  const name = tp.namedChildren.find((c2) => c2?.type === "identifier")?.text;
136030
136399
  if (!name)
136031
136400
  continue;
136032
- const usedElsewhere = usedInReturn.has(name) || usedInBody.has(name);
136033
- if (!usedInParams.has(name) && usedElsewhere) {
136401
+ if (!usedInParams.has(name) && usedInReturn.has(name)) {
136034
136402
  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.`);
136035
136403
  }
136036
136404
  }
@@ -136143,6 +136511,9 @@ var init_native_method_not_wrapped = __esm({
136143
136511
  });
136144
136512
 
136145
136513
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/nested-generic-parameter.js
136514
+ function genericBaseName2(generic) {
136515
+ return generic.namedChildren.find((c2) => c2?.type === "identifier")?.text ?? "";
136516
+ }
136146
136517
  function asGenericName(typeNode) {
136147
136518
  if (!typeNode)
136148
136519
  return null;
@@ -136158,16 +136529,19 @@ function hasNestedGeneric(typeNode) {
136158
136529
  const generic = asGenericName(typeNode);
136159
136530
  if (!generic)
136160
136531
  return false;
136532
+ if (DELEGATE_OR_EXPRESSION_GENERICS.has(genericBaseName2(generic)))
136533
+ return false;
136161
136534
  const args = generic.namedChildren.find((c2) => c2?.type === "type_argument_list");
136162
136535
  if (!args)
136163
136536
  return false;
136164
136537
  return args.namedChildren.some((arg) => asGenericName(arg) !== null);
136165
136538
  }
136166
- var csharpNestedGenericParameterVisitor;
136539
+ var DELEGATE_OR_EXPRESSION_GENERICS, csharpNestedGenericParameterVisitor;
136167
136540
  var init_nested_generic_parameter = __esm({
136168
136541
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/nested-generic-parameter.js"() {
136169
136542
  "use strict";
136170
136543
  init_types2();
136544
+ DELEGATE_OR_EXPRESSION_GENERICS = /* @__PURE__ */ new Set(["Func", "Action", "Predicate", "Expression"]);
136171
136545
  csharpNestedGenericParameterVisitor = {
136172
136546
  ruleKey: "code-quality/deterministic/nested-generic-parameter",
136173
136547
  languages: ["csharp"],
@@ -136287,6 +136661,39 @@ function enclosingTypeBody(node2) {
136287
136661
  const body = node2.parent;
136288
136662
  return body?.type === "declaration_list" ? body : null;
136289
136663
  }
136664
+ function isZeroArgCallTo(expr, getName) {
136665
+ if (expr?.type !== "invocation_expression")
136666
+ return false;
136667
+ const fn = expr.childForFieldName("function") ?? expr.namedChildren[0];
136668
+ const fnText = fn?.text;
136669
+ if (fnText !== getName && fnText !== `this.${getName}`)
136670
+ return false;
136671
+ const args = expr.childForFieldName("arguments") ?? expr.namedChildren.find((c2) => c2?.type === "argument_list");
136672
+ const argCount = args?.namedChildren.filter((c2) => c2?.type === "argument").length ?? 0;
136673
+ return argCount === 0;
136674
+ }
136675
+ function getterDelegatesTo(prop, getName) {
136676
+ const propArrow = prop.namedChildren.find((c2) => c2?.type === "arrow_expression_clause");
136677
+ if (propArrow)
136678
+ return isZeroArgCallTo(propArrow.namedChildren.find(Boolean), getName);
136679
+ const accessorList = prop.namedChildren.find((c2) => c2?.type === "accessor_list");
136680
+ if (!accessorList)
136681
+ return false;
136682
+ const getter = accessorList.namedChildren.find((a) => a?.type === "accessor_declaration" && a.children.some((c2) => c2?.text === "get"));
136683
+ if (!getter)
136684
+ return false;
136685
+ const getArrow = getter.namedChildren.find((c2) => c2?.type === "arrow_expression_clause");
136686
+ if (getArrow)
136687
+ return isZeroArgCallTo(getArrow.namedChildren.find(Boolean), getName);
136688
+ const block = getter.namedChildren.find((c2) => c2?.type === "block");
136689
+ if (block) {
136690
+ const stmts = block.namedChildren.filter((c2) => c2?.type !== "comment");
136691
+ if (stmts.length === 1 && stmts[0]?.type === "return_statement") {
136692
+ return isZeroArgCallTo(stmts[0].namedChildren.find(Boolean), getName);
136693
+ }
136694
+ }
136695
+ return false;
136696
+ }
136290
136697
  var csharpPropertyNameMatchesGetMethodVisitor;
136291
136698
  var init_property_name_matches_get_method = __esm({
136292
136699
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/property-name-matches-get-method.js"() {
@@ -136314,6 +136721,8 @@ var init_property_name_matches_get_method = __esm({
136314
136721
  });
136315
136722
  if (!hasGetMethod)
136316
136723
  return null;
136724
+ if (getterDelegatesTo(node2, target))
136725
+ return null;
136317
136726
  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.`);
136318
136727
  }
136319
136728
  };
@@ -136321,6 +136730,50 @@ var init_property_name_matches_get_method = __esm({
136321
136730
  });
136322
136731
 
136323
136732
  // packages/analyzer/dist/rules/code-quality/visitors/csharp/public-const-versioning-hazard.js
136733
+ function constFieldNames(fieldDecl) {
136734
+ const names = [];
136735
+ const varDecl = fieldDecl.namedChildren.find((c2) => c2?.type === "variable_declaration");
136736
+ if (!varDecl)
136737
+ return names;
136738
+ for (const vd of varDecl.namedChildren) {
136739
+ if (vd?.type !== "variable_declarator")
136740
+ continue;
136741
+ const n = vd.childForFieldName("name")?.text ?? vd.namedChildren.find((c2) => c2?.type === "identifier")?.text;
136742
+ if (n)
136743
+ names.push(n);
136744
+ }
136745
+ return names;
136746
+ }
136747
+ function referencedBySiblingConst(node2, names) {
136748
+ let root2 = node2;
136749
+ while (root2.parent)
136750
+ root2 = root2.parent;
136751
+ let found = false;
136752
+ const scan3 = (n) => {
136753
+ if (found)
136754
+ return;
136755
+ if (n.type === "field_declaration" && n.id !== node2.id && hasCSharpModifier(n, "const")) {
136756
+ const varDecl = n.namedChildren.find((c2) => c2?.type === "variable_declaration");
136757
+ if (varDecl) {
136758
+ for (const vd of varDecl.namedChildren) {
136759
+ if (vd?.type !== "variable_declarator")
136760
+ continue;
136761
+ const declaredName = vd.childForFieldName("name");
136762
+ walkCSharp(vd, (id) => {
136763
+ if (id.type === "identifier" && id.id !== declaredName?.id && names.includes(id.text)) {
136764
+ found = true;
136765
+ }
136766
+ });
136767
+ }
136768
+ }
136769
+ }
136770
+ for (const c2 of n.namedChildren)
136771
+ if (c2)
136772
+ scan3(c2);
136773
+ };
136774
+ scan3(root2);
136775
+ return found;
136776
+ }
136324
136777
  var csharpPublicConstVersioningHazardVisitor;
136325
136778
  var init_public_const_versioning_hazard = __esm({
136326
136779
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/public-const-versioning-hazard.js"() {
@@ -136336,6 +136789,9 @@ var init_public_const_versioning_hazard = __esm({
136336
136789
  return null;
136337
136790
  if (!hasCSharpModifier(node2, "public") && !hasCSharpModifier(node2, "protected"))
136338
136791
  return null;
136792
+ const names = constFieldNames(node2);
136793
+ if (names.length > 0 && referencedBySiblingConst(node2, names))
136794
+ return null;
136339
136795
  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.");
136340
136796
  }
136341
136797
  };
@@ -137011,12 +137467,12 @@ var init_missing_access_modifier = __esm({
137011
137467
  return null;
137012
137468
  if (node2.parent?.parent?.type === "interface_declaration")
137013
137469
  return null;
137470
+ if (node2.namedChildren.some((c2) => c2?.type === "explicit_interface_specifier"))
137471
+ return null;
137014
137472
  if (node2.type === "method_declaration") {
137015
137473
  const nm = node2.childForFieldName("name");
137016
137474
  if (nm?.type === "qualified_name" || (nm?.text ?? "").includes("."))
137017
137475
  return null;
137018
- if (node2.namedChildren.some((c2) => c2?.type === "explicit_interface_specifier"))
137019
- return null;
137020
137476
  }
137021
137477
  if (node2.type === "constructor_declaration" && mods.includes("static"))
137022
137478
  return null;
@@ -137080,7 +137536,7 @@ function topLevelTypeIdentifiers(root2) {
137080
137536
  const body = m.namedChildren.find((c2) => c2?.type === "declaration_list");
137081
137537
  if (body)
137082
137538
  collectFrom(body);
137083
- } else if (TYPE_DECL_TYPES2.has(m.type)) {
137539
+ } else if (TYPE_DECL_TYPES3.has(m.type)) {
137084
137540
  const id = m.childForFieldName("name");
137085
137541
  if (id)
137086
137542
  out.push(id);
@@ -137093,6 +137549,8 @@ function topLevelTypeIdentifiers(root2) {
137093
137549
  function nameMatches(fileName, typeName2) {
137094
137550
  if (fileName === typeName2)
137095
137551
  return true;
137552
+ if (typeName2 === `${fileName}Attribute`)
137553
+ return true;
137096
137554
  const brace = fileName.indexOf("{");
137097
137555
  if (brace > 0 && fileName.slice(0, brace) === typeName2)
137098
137556
  return true;
@@ -137101,12 +137559,12 @@ function nameMatches(fileName, typeName2) {
137101
137559
  return true;
137102
137560
  return false;
137103
137561
  }
137104
- var TYPE_DECL_TYPES2, csharpFilenameTypeMismatchVisitor;
137562
+ var TYPE_DECL_TYPES3, csharpFilenameTypeMismatchVisitor;
137105
137563
  var init_csharp_filename_type_mismatch = __esm({
137106
137564
  "packages/analyzer/dist/rules/code-quality/visitors/csharp/csharp-filename-type-mismatch.js"() {
137107
137565
  "use strict";
137108
137566
  init_types2();
137109
- TYPE_DECL_TYPES2 = /* @__PURE__ */ new Set([
137567
+ TYPE_DECL_TYPES3 = /* @__PURE__ */ new Set([
137110
137568
  "class_declaration",
137111
137569
  "struct_declaration",
137112
137570
  "interface_declaration",
@@ -137120,13 +137578,18 @@ var init_csharp_filename_type_mismatch = __esm({
137120
137578
  nodeTypes: ["compilation_unit"],
137121
137579
  visit(node2, filePath, sourceCode) {
137122
137580
  const base2 = filePath.split("/").pop() ?? "";
137123
- const fileName = base2.replace(/\.[^.]*$/, "");
137581
+ const lower = base2.toLowerCase();
137582
+ const compoundExt = [".cshtml.cs", ".razor.cs"].find((ext2) => lower.endsWith(ext2));
137583
+ const fileName = compoundExt ? base2.slice(0, base2.length - compoundExt.length) : base2.replace(/\.[^.]*$/, "");
137124
137584
  if (!fileName)
137125
137585
  return null;
137586
+ const migrationName = fileName.match(/^\d{14}_(.+)$/)?.[1];
137587
+ const candidateNames2 = migrationName ? [fileName, migrationName] : [fileName];
137126
137588
  const identifiers = topLevelTypeIdentifiers(node2);
137127
137589
  if (identifiers.length === 0)
137128
137590
  return null;
137129
- if (identifiers.some((id) => nameMatches(fileName, id.text)))
137591
+ const matches = (id) => candidateNames2.some((fn) => nameMatches(fn, id.text)) || compoundExt !== void 0 && id.text === `${fileName}Model`;
137592
+ if (identifiers.some(matches))
137130
137593
  return null;
137131
137594
  const first2 = identifiers[0];
137132
137595
  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.`);
@@ -137579,7 +138042,7 @@ var init_csharp6 = __esm({
137579
138042
  init_cognitive_complexity3();
137580
138043
  init_collapsible_else_if2();
137581
138044
  init_collapsible_if3();
137582
- init_commented_out_code3();
138045
+ init_commented_out_code4();
137583
138046
  init_comparison_of_constant2();
137584
138047
  init_console_log2();
137585
138048
  init_contradictory_boolean_expression2();
@@ -137662,7 +138125,6 @@ var init_csharp6 = __esm({
137662
138125
  init_unnecessary_namespace_qualifier2();
137663
138126
  init_unsafe_any_usage2();
137664
138127
  init_unused_collection2();
137665
- init_unused_function_parameter2();
137666
138128
  init_unused_private_member2();
137667
138129
  init_unused_private_method2();
137668
138130
  init_unused_private_nested_class2();
@@ -137921,7 +138383,6 @@ var init_csharp6 = __esm({
137921
138383
  csharpUnnecessaryNamespaceQualifierVisitor,
137922
138384
  csharpUnsafeAnyUsageVisitor,
137923
138385
  csharpUnusedCollectionVisitor,
137924
- csharpUnusedFunctionParameterVisitor,
137925
138386
  csharpUnusedPrivateMemberVisitor,
137926
138387
  csharpUnusedPrivateMethodVisitor,
137927
138388
  csharpUnusedPrivateNestedClassVisitor,
@@ -140103,6 +140564,11 @@ function allElementsConstant(creation) {
140103
140564
  return false;
140104
140565
  return elements.every((e) => LITERAL_TYPES6.has(e.type));
140105
140566
  }
140567
+ function isGeneratedOrMigrationFile(filePath) {
140568
+ if (/(^|[\\/])Migrations[\\/]/.test(filePath))
140569
+ return true;
140570
+ return /\.(designer|g|g\.i|generated)\.cs$/i.test(filePath);
140571
+ }
140106
140572
  var LITERAL_TYPES6, csharpConstantArrayArgumentVisitor;
140107
140573
  var init_constant_array_argument = __esm({
140108
140574
  "packages/analyzer/dist/rules/performance/visitors/csharp/constant-array-argument.js"() {
@@ -140123,6 +140589,8 @@ var init_constant_array_argument = __esm({
140123
140589
  languages: ["csharp"],
140124
140590
  nodeTypes: ["array_creation_expression", "implicit_array_creation_expression"],
140125
140591
  visit(node2, filePath, sourceCode) {
140592
+ if (isGeneratedOrMigrationFile(filePath))
140593
+ return null;
140126
140594
  if (node2.parent?.type !== "argument")
140127
140595
  return null;
140128
140596
  if (!getCSharpEnclosingFunction(node2))
@@ -140911,7 +141379,36 @@ var init_missing_finally_cleanup2 = __esm({
140911
141379
  });
140912
141380
 
140913
141381
  // packages/analyzer/dist/rules/reliability/visitors/csharp/unsafe-json-parse.js
140914
- var JSON_PARSERS, csharpUnsafeJsonParseVisitor;
141382
+ function bodyDeclaresMember(body, name) {
141383
+ for (const member of body.namedChildren) {
141384
+ if (!member)
141385
+ continue;
141386
+ if (member.type === "property_declaration") {
141387
+ if (member.childForFieldName("name")?.text === name)
141388
+ return true;
141389
+ } else if (member.type === "field_declaration") {
141390
+ const decl = member.namedChildren.find((c2) => c2?.type === "variable_declaration");
141391
+ for (const d3 of decl?.namedChildren ?? []) {
141392
+ if (d3?.type === "variable_declarator" && d3.childForFieldName("name")?.text === name)
141393
+ return true;
141394
+ }
141395
+ }
141396
+ }
141397
+ return false;
141398
+ }
141399
+ function enclosingTypeDeclaresMember(node2, name) {
141400
+ let current = node2.parent;
141401
+ while (current) {
141402
+ if (TYPE_DECLS2.has(current.type)) {
141403
+ const body = current.childForFieldName("body");
141404
+ if (body && bodyDeclaresMember(body, name))
141405
+ return true;
141406
+ }
141407
+ current = current.parent;
141408
+ }
141409
+ return false;
141410
+ }
141411
+ var JSON_PARSERS, TYPE_DECLS2, csharpUnsafeJsonParseVisitor;
140915
141412
  var init_unsafe_json_parse3 = __esm({
140916
141413
  "packages/analyzer/dist/rules/reliability/visitors/csharp/unsafe-json-parse.js"() {
140917
141414
  "use strict";
@@ -140926,15 +141423,25 @@ var init_unsafe_json_parse3 = __esm({
140926
141423
  JsonDocument: /* @__PURE__ */ new Set(["Parse", "ParseAsync"]),
140927
141424
  JsonNode: /* @__PURE__ */ new Set(["Parse"])
140928
141425
  };
141426
+ TYPE_DECLS2 = /* @__PURE__ */ new Set([
141427
+ "class_declaration",
141428
+ "struct_declaration",
141429
+ "record_declaration",
141430
+ "record_struct_declaration"
141431
+ ]);
140929
141432
  csharpUnsafeJsonParseVisitor = {
140930
141433
  ruleKey: "reliability/deterministic/unsafe-json-parse",
140931
141434
  languages: ["csharp"],
140932
141435
  nodeTypes: ["invocation_expression"],
140933
141436
  visit(node2, filePath, sourceCode) {
140934
- const receiver = simpleTypeName5(getCSharpReceiver(node2));
141437
+ const rawReceiver = getCSharpReceiver(node2);
141438
+ const receiver = simpleTypeName5(rawReceiver);
140935
141439
  const method = getCSharpMethodName(node2);
140936
141440
  if (!JSON_PARSERS[receiver]?.has(method))
140937
141441
  return null;
141442
+ const bareReceiver = rawReceiver.startsWith("this.") ? rawReceiver.slice(5) : rawReceiver;
141443
+ if (!bareReceiver.includes(".") && enclosingTypeDeclaresMember(node2, bareReceiver))
141444
+ return null;
140938
141445
  const firstArg = getCSharpArguments(node2)[0];
140939
141446
  if (firstArg?.type === "invocation_expression") {
140940
141447
  const innerReceiver = simpleTypeName5(getCSharpReceiver(firstArg));
@@ -142641,18 +143148,10 @@ var init_logger_field_naming = __esm({
142641
143148
  csharpLoggerFieldNamingVisitor = {
142642
143149
  ruleKey: "style/deterministic/logger-field-naming",
142643
143150
  languages: ["csharp"],
142644
- nodeTypes: ["field_declaration", "property_declaration"],
143151
+ nodeTypes: ["field_declaration"],
142645
143152
  visit(node2, filePath, sourceCode) {
142646
143153
  if (isAutoGeneratedCSharpFile(sourceCode))
142647
143154
  return null;
142648
- if (node2.type === "property_declaration") {
142649
- if (!isLoggerType(node2.childForFieldName("type")))
142650
- return null;
142651
- const name = node2.childForFieldName("name");
142652
- if (!name || ACCEPTED.test(name.text))
142653
- return null;
142654
- 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'.`);
142655
- }
142656
143155
  const declaration = node2.namedChildren.find((c2) => c2?.type === "variable_declaration");
142657
143156
  if (!declaration)
142658
143157
  return null;
@@ -143460,15 +143959,28 @@ function isNestedInType(node2) {
143460
143959
  const parent = node2.parent;
143461
143960
  if (parent?.type !== "declaration_list")
143462
143961
  return false;
143463
- return parent.parent != null && TYPE_DECL_TYPES3.has(parent.parent.type);
143962
+ return parent.parent != null && TYPE_DECL_TYPES4.has(parent.parent.type);
143464
143963
  }
143465
- var TYPE_DECL_TYPES3, csharpNestedTypePubliclyVisibleVisitor;
143964
+ function isConstantContainer(node2) {
143965
+ if (node2.type !== "class_declaration")
143966
+ return false;
143967
+ if (!hasCSharpModifier(node2, "static"))
143968
+ return false;
143969
+ const body = node2.childForFieldName("body");
143970
+ if (!body)
143971
+ return false;
143972
+ const members = body.namedChildren.filter((c2) => c2 && c2.type !== "comment");
143973
+ if (members.length === 0)
143974
+ return false;
143975
+ return members.every((m) => m?.type === "field_declaration" && (hasCSharpModifier(m, "const") || hasCSharpModifier(m, "static") && hasCSharpModifier(m, "readonly")));
143976
+ }
143977
+ var TYPE_DECL_TYPES4, csharpNestedTypePubliclyVisibleVisitor;
143466
143978
  var init_nested_type_publicly_visible = __esm({
143467
143979
  "packages/analyzer/dist/rules/architecture/visitors/csharp/nested-type-publicly-visible.js"() {
143468
143980
  "use strict";
143469
143981
  init_types2();
143470
143982
  init_csharp_helpers();
143471
- TYPE_DECL_TYPES3 = /* @__PURE__ */ new Set([
143983
+ TYPE_DECL_TYPES4 = /* @__PURE__ */ new Set([
143472
143984
  "class_declaration",
143473
143985
  "struct_declaration",
143474
143986
  "interface_declaration",
@@ -143486,6 +143998,8 @@ var init_nested_type_publicly_visible = __esm({
143486
143998
  return null;
143487
143999
  if (getCSharpAttributeNames(node2).length > 0)
143488
144000
  return null;
144001
+ if (isConstantContainer(node2))
144002
+ return null;
143489
144003
  const name = node2.childForFieldName("name")?.text ?? "type";
143490
144004
  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.`);
143491
144005
  }
@@ -143573,6 +144087,8 @@ var init_type_outside_namespace = __esm({
143573
144087
 
143574
144088
  // packages/analyzer/dist/rules/architecture/visitors/csharp/_uri-helpers.js
143575
144089
  function nameLooksLikeUri(name) {
144090
+ if (PREPOSITION_BEFORE_URI.test(name))
144091
+ return false;
143576
144092
  if (URI_NAME_PATTERN.test(name))
143577
144093
  return true;
143578
144094
  return /(Url|Uri|Endpoint|Webhook)$/.test(name);
@@ -143580,11 +144096,12 @@ function nameLooksLikeUri(name) {
143580
144096
  function isStringType(typeNode) {
143581
144097
  return typeNode?.type === "predefined_type" && typeNode.text === "string";
143582
144098
  }
143583
- var URI_NAME_PATTERN;
144099
+ var URI_NAME_PATTERN, PREPOSITION_BEFORE_URI;
143584
144100
  var init_uri_helpers = __esm({
143585
144101
  "packages/analyzer/dist/rules/architecture/visitors/csharp/_uri-helpers.js"() {
143586
144102
  "use strict";
143587
144103
  URI_NAME_PATTERN = /(^|[^a-z])(url|uri|uris|urls|endpoint|endpoints|webhook|webhookurl|callbackurl|redirecturi|redirecturl|hreflink|baseurl|baseaddress)$/i;
144104
+ PREPOSITION_BEFORE_URI = /(In|As|Of|At|By|Within)(Url|Uri|Uris|Urls|Endpoint|Endpoints|Webhook)$/;
143588
144105
  }
143589
144106
  });
143590
144107
 
@@ -143622,13 +144139,22 @@ var init_uri_parameter_as_string = __esm({
143622
144139
  });
143623
144140
 
143624
144141
  // packages/analyzer/dist/rules/architecture/visitors/csharp/uri-property-as-string.js
143625
- var csharpUriPropertyAsStringVisitor;
144142
+ function initializerProvesNotUri(valueNode) {
144143
+ if (!valueNode || !isCSharpStringNode(valueNode))
144144
+ return false;
144145
+ const text4 = getCSharpStringText(valueNode);
144146
+ if (text4 == null || text4.length === 0)
144147
+ return false;
144148
+ return !text4.includes("://");
144149
+ }
144150
+ var BINDING_ATTRIBUTES, csharpUriPropertyAsStringVisitor;
143626
144151
  var init_uri_property_as_string = __esm({
143627
144152
  "packages/analyzer/dist/rules/architecture/visitors/csharp/uri-property-as-string.js"() {
143628
144153
  "use strict";
143629
144154
  init_types2();
143630
144155
  init_csharp_helpers();
143631
144156
  init_uri_helpers();
144157
+ BINDING_ATTRIBUTES = /* @__PURE__ */ new Set(["BindProperty", "FromQuery", "FromRoute", "FromForm", "ModelBinder"]);
143632
144158
  csharpUriPropertyAsStringVisitor = {
143633
144159
  ruleKey: "architecture/deterministic/uri-property-as-string",
143634
144160
  languages: ["csharp"],
@@ -143641,6 +144167,10 @@ var init_uri_property_as_string = __esm({
143641
144167
  const name = node2.childForFieldName("name")?.text;
143642
144168
  if (!name || !nameLooksLikeUri(name))
143643
144169
  return null;
144170
+ if (getCSharpAttributeNames(node2).some((a) => BINDING_ATTRIBUTES.has(a)))
144171
+ return null;
144172
+ if (initializerProvesNotUri(node2.childForFieldName("value")))
144173
+ return null;
143644
144174
  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.`);
143645
144175
  }
143646
144176
  };
@@ -143678,6 +144208,21 @@ function isModelType(classDecl) {
143678
144208
  const name = classDecl.childForFieldName("name")?.text ?? "";
143679
144209
  return MODEL_NAME_SUFFIX.test(name);
143680
144210
  }
144211
+ function isServerConstructed(classDecl, body) {
144212
+ for (const member of body.namedChildren) {
144213
+ if (member?.type === "constructor_declaration") {
144214
+ const params = member.childForFieldName("parameters");
144215
+ if (params && params.namedChildCount > 0)
144216
+ return true;
144217
+ }
144218
+ if (member?.type === "method_declaration" && hasCSharpModifier(member, "static")) {
144219
+ const mName = member.childForFieldName("name")?.text ?? "";
144220
+ if (mName === "Create" || mName.startsWith("From"))
144221
+ return true;
144222
+ }
144223
+ }
144224
+ return false;
144225
+ }
143681
144226
  function flaggablePropertyType(typeNode) {
143682
144227
  if (typeNode?.type !== "predefined_type")
143683
144228
  return null;
@@ -143690,7 +144235,7 @@ var init_value_type_action_param_under_posting = __esm({
143690
144235
  "use strict";
143691
144236
  init_types2();
143692
144237
  init_csharp_helpers();
143693
- MODEL_NAME_SUFFIX = /(Request|Dto|DTO|Model|ViewModel|Input|Form|Command|Query|Payload)$/;
144238
+ MODEL_NAME_SUFFIX = /(Request|Input|Form|Command|Query|Payload)$/;
143694
144239
  SCALAR_VALUE_TYPES = /* @__PURE__ */ new Set(["int", "long", "short", "byte", "uint", "ulong", "ushort", "sbyte", "decimal", "double", "float", "bool", "char"]);
143695
144240
  csharpValueTypeActionParamUnderPostingVisitor = {
143696
144241
  ruleKey: "architecture/deterministic/value-type-action-param-under-posting",
@@ -143702,6 +144247,8 @@ var init_value_type_action_param_under_posting = __esm({
143702
144247
  const body = node2.childForFieldName("body");
143703
144248
  if (!body)
143704
144249
  return null;
144250
+ if (isServerConstructed(node2, body))
144251
+ return null;
143705
144252
  for (const member of body.namedChildren) {
143706
144253
  if (member?.type !== "property_declaration")
143707
144254
  continue;
@@ -143729,6 +144276,22 @@ var init_value_type_action_param_under_posting = __esm({
143729
144276
  });
143730
144277
 
143731
144278
  // packages/analyzer/dist/rules/architecture/visitors/csharp/action-missing-producesresponsetype.js
144279
+ function excludedFromApiExplorer(node2) {
144280
+ for (const child of node2.children) {
144281
+ if (child?.type !== "attribute_list")
144282
+ continue;
144283
+ for (const attr of child.namedChildren) {
144284
+ if (attr?.type !== "attribute")
144285
+ continue;
144286
+ const name = (attr.childForFieldName("name")?.text ?? "").split(".").pop();
144287
+ if (name !== "ApiExplorerSettings")
144288
+ continue;
144289
+ if (/\bIgnoreApi\s*=\s*true\b/i.test(attr.text))
144290
+ return true;
144291
+ }
144292
+ }
144293
+ return false;
144294
+ }
143732
144295
  function attributeNames7(node2) {
143733
144296
  const names = [];
143734
144297
  for (const child of node2.children) {
@@ -143776,6 +144339,8 @@ var init_action_missing_producesresponsetype = __esm({
143776
144339
  const classAttrs = attributeNames7(cls);
143777
144340
  if (classAttrs.some((a) => a.startsWith("ProducesResponseType") || a === "ProducesDefaultResponseType"))
143778
144341
  return null;
144342
+ if (excludedFromApiExplorer(node2) || excludedFromApiExplorer(cls))
144343
+ return null;
143779
144344
  const name = node2.childForFieldName("name");
143780
144345
  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.");
143781
144346
  }
@@ -145718,52 +146283,96 @@ var init_pyproject_checker = __esm({
145718
146283
  // packages/analyzer/dist/roslyn-host-client.js
145719
146284
  import { spawn as spawn4 } from "child_process";
145720
146285
  import { existsSync as existsSync10 } from "fs";
146286
+ import { tmpdir } from "os";
145721
146287
  import { dirname as dirname9, resolve as resolve9 } from "path";
145722
146288
  import { fileURLToPath as fileURLToPath5 } from "url";
145723
146289
  function resolveRoslynHostBinary() {
145724
146290
  const override = process.env[HOST_BINARY_ENV];
145725
146291
  if (override)
145726
146292
  return existsSync10(override) ? override : null;
145727
- const here = dirname9(fileURLToPath5(import.meta.url));
145728
- const rel = "tools/csharp-roslyn-host/bin/Release/net8.0/csharp-roslyn-host";
145729
- for (const up of ["../../..", "../../../..", "../../../../.."]) {
145730
- const candidate = resolve9(here, up, rel);
145731
- if (existsSync10(candidate))
145732
- return candidate;
146293
+ const rels = [
146294
+ "roslyn-host/csharp-roslyn-host.dll",
146295
+ "tools/csharp-roslyn-host/bin/Release/net8.0/csharp-roslyn-host"
146296
+ ];
146297
+ let dir = dirname9(fileURLToPath5(import.meta.url));
146298
+ for (let i = 0; i < 12; i++) {
146299
+ for (const rel of rels) {
146300
+ const candidate = resolve9(dir, rel);
146301
+ if (existsSync10(candidate))
146302
+ return candidate;
146303
+ }
146304
+ const parent = dirname9(dir);
146305
+ if (parent === dir)
146306
+ break;
146307
+ dir = parent;
145733
146308
  }
145734
146309
  return null;
145735
146310
  }
145736
146311
  function invokeHost(bin, request) {
145737
146312
  return new Promise((resolvePromise, reject) => {
145738
- const child = spawn4(bin, [], { stdio: ["pipe", "pipe", "pipe"] });
146313
+ const viaDotnet = bin.endsWith(".dll");
146314
+ const cmd = viaDotnet ? "dotnet" : bin;
146315
+ const args = viaDotnet ? [bin] : [];
146316
+ const child = spawn4(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd: tmpdir() });
145739
146317
  let stdout = "";
145740
146318
  let stderr = "";
146319
+ let settled = false;
146320
+ const fail4 = (err) => {
146321
+ if (settled)
146322
+ return;
146323
+ settled = true;
146324
+ reject(err);
146325
+ };
146326
+ const succeed = (violations) => {
146327
+ if (settled)
146328
+ return;
146329
+ settled = true;
146330
+ resolvePromise(violations);
146331
+ };
145741
146332
  child.stdout.setEncoding("utf8");
145742
146333
  child.stderr.setEncoding("utf8");
145743
146334
  child.stdout.on("data", (d3) => stdout += d3);
145744
146335
  child.stderr.on("data", (d3) => stderr += d3);
145745
- child.on("error", (e) => reject(new RoslynHostUnavailableError(`Failed to start the Roslyn host (${bin}): ${e.message}. Is the .NET runtime installed?`)));
145746
- child.on("close", () => {
145747
- const line = stdout.split("\n").find((l) => l.trim());
145748
- if (!line) {
145749
- reject(new Error(`Roslyn host produced no output.${stderr ? ` stderr: ${stderr}` : ""}`));
146336
+ child.on("error", (e) => fail4(new RoslynHostUnavailableError(`Failed to start the Roslyn host (${viaDotnet ? `dotnet ${bin}` : bin}): ${e.message}. Is the .NET runtime installed?`)));
146337
+ child.stdin.on("error", () => {
146338
+ });
146339
+ child.on("close", (code, signalName) => {
146340
+ if (code !== 0 || signalName) {
146341
+ const reason = stderr.trim() || stdout.trim() || "(no output)";
146342
+ fail4(new RoslynHostUnavailableError(`The Roslyn host exited ${signalName ? `via ${signalName}` : `with code ${code}`} before responding. Is a compatible .NET runtime installed? Host output: ${reason}`));
146343
+ return;
146344
+ }
146345
+ const lines = stdout.split("\n").map((l) => l.trim()).filter(Boolean);
146346
+ if (lines.length === 0) {
146347
+ fail4(new Error(`Roslyn host produced no output.${stderr ? ` stderr: ${stderr}` : ""}`));
145750
146348
  return;
145751
146349
  }
145752
146350
  let resp;
145753
- try {
145754
- resp = JSON.parse(line);
145755
- } catch {
145756
- reject(new Error(`Roslyn host returned invalid JSON: ${line}`));
146351
+ for (const l of lines) {
146352
+ try {
146353
+ const parsed = JSON.parse(l);
146354
+ if (parsed && typeof parsed.ok === "boolean") {
146355
+ resp = parsed;
146356
+ break;
146357
+ }
146358
+ } catch {
146359
+ }
146360
+ }
146361
+ if (!resp) {
146362
+ fail4(new Error(`Roslyn host returned invalid JSON: ${lines[0]}`));
145757
146363
  return;
145758
146364
  }
145759
146365
  if (!resp.ok) {
145760
- reject(new Error(`Roslyn host error: ${resp.error ?? "unknown"}`));
146366
+ fail4(new Error(`Roslyn host error: ${resp.error ?? "unknown"}`));
145761
146367
  return;
145762
146368
  }
145763
- resolvePromise(resp.violations ?? []);
146369
+ succeed(resp.violations ?? []);
145764
146370
  });
145765
- child.stdin.write(JSON.stringify(request) + "\n");
145766
- child.stdin.end();
146371
+ try {
146372
+ child.stdin.write(JSON.stringify(request) + "\n");
146373
+ child.stdin.end();
146374
+ } catch {
146375
+ }
145767
146376
  });
145768
146377
  }
145769
146378
  function runRoslynWorkspace(projectPath, rules) {
@@ -146568,7 +147177,7 @@ var init_flow_service = __esm({
146568
147177
  });
146569
147178
 
146570
147179
  // packages/core/dist/progress.js
146571
- function buildAnalysisSteps(enabledCategories, enableLlmRules) {
147180
+ function buildAnalysisSteps(enabledCategories, enableLlmRules, hasCSharp) {
146572
147181
  const steps = [
146573
147182
  { key: "parse", label: "Parsing repository" }
146574
147183
  ];
@@ -146579,6 +147188,9 @@ function buildAnalysisSteps(enabledCategories, enableLlmRules) {
146579
147188
  for (const domain of activeDomains) {
146580
147189
  steps.push({ key: domain, label: `${DOMAIN_LABELS[domain]} checks` });
146581
147190
  }
147191
+ if (hasCSharp) {
147192
+ steps.push({ key: "csharp", label: "C# semantic analysis" });
147193
+ }
146582
147194
  steps.push({ key: "persist", label: "Saving results" });
146583
147195
  return steps;
146584
147196
  }
@@ -146621,6 +147233,23 @@ var init_progress = __esm({
146621
147233
  this.emit();
146622
147234
  }
146623
147235
  }
147236
+ /**
147237
+ * Insert a step at runtime, just before `persist`, if it isn't already there.
147238
+ * Used for phases we can't declare up front — e.g. the C# semantic tier only
147239
+ * exists when the repo actually has C#, which isn't known until files are read.
147240
+ * No-op if the key already exists.
147241
+ */
147242
+ ensureStep(key4, label) {
147243
+ if (this.steps.some((s) => s.key === key4))
147244
+ return;
147245
+ const persistIdx = this.steps.findIndex((s) => s.key === "persist");
147246
+ const step = { key: key4, label, status: "pending" };
147247
+ if (persistIdx === -1)
147248
+ this.steps.push(step);
147249
+ else
147250
+ this.steps.splice(persistIdx, 0, step);
147251
+ this.emit();
147252
+ }
146624
147253
  setStatus(key4, status, detail) {
146625
147254
  const step = this.steps.find((s) => s.key === key4);
146626
147255
  if (step) {
@@ -146694,7 +147323,7 @@ var require_windows = __commonJS({
146694
147323
  "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js"(exports, module) {
146695
147324
  module.exports = isexe;
146696
147325
  isexe.sync = sync;
146697
- var fs70 = __require("fs");
147326
+ var fs71 = __require("fs");
146698
147327
  function checkPathExt(path80, options) {
146699
147328
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
146700
147329
  if (!pathext) {
@@ -146719,12 +147348,12 @@ var require_windows = __commonJS({
146719
147348
  return checkPathExt(path80, options);
146720
147349
  }
146721
147350
  function isexe(path80, options, cb) {
146722
- fs70.stat(path80, function(er, stat) {
147351
+ fs71.stat(path80, function(er, stat) {
146723
147352
  cb(er, er ? false : checkStat(stat, path80, options));
146724
147353
  });
146725
147354
  }
146726
147355
  function sync(path80, options) {
146727
- return checkStat(fs70.statSync(path80), path80, options);
147356
+ return checkStat(fs71.statSync(path80), path80, options);
146728
147357
  }
146729
147358
  }
146730
147359
  });
@@ -146734,14 +147363,14 @@ var require_mode = __commonJS({
146734
147363
  "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js"(exports, module) {
146735
147364
  module.exports = isexe;
146736
147365
  isexe.sync = sync;
146737
- var fs70 = __require("fs");
147366
+ var fs71 = __require("fs");
146738
147367
  function isexe(path80, options, cb) {
146739
- fs70.stat(path80, function(er, stat) {
147368
+ fs71.stat(path80, function(er, stat) {
146740
147369
  cb(er, er ? false : checkStat(stat, options));
146741
147370
  });
146742
147371
  }
146743
147372
  function sync(path80, options) {
146744
- return checkStat(fs70.statSync(path80), options);
147373
+ return checkStat(fs71.statSync(path80), options);
146745
147374
  }
146746
147375
  function checkStat(stat, options) {
146747
147376
  return stat.isFile() && checkMode(stat, options);
@@ -146765,7 +147394,7 @@ var require_mode = __commonJS({
146765
147394
  // node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js
146766
147395
  var require_isexe = __commonJS({
146767
147396
  "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js"(exports, module) {
146768
- var fs70 = __require("fs");
147397
+ var fs71 = __require("fs");
146769
147398
  var core2;
146770
147399
  if (process.platform === "win32" || global.TESTING_WINDOWS) {
146771
147400
  core2 = require_windows();
@@ -147029,16 +147658,16 @@ var require_shebang_command = __commonJS({
147029
147658
  var require_readShebang = __commonJS({
147030
147659
  "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
147031
147660
  "use strict";
147032
- var fs70 = __require("fs");
147661
+ var fs71 = __require("fs");
147033
147662
  var shebangCommand = require_shebang_command();
147034
147663
  function readShebang(command) {
147035
147664
  const size = 150;
147036
147665
  const buffer = Buffer.alloc(size);
147037
147666
  let fd;
147038
147667
  try {
147039
- fd = fs70.openSync(command, "r");
147040
- fs70.readSync(fd, buffer, 0, size, 0);
147041
- fs70.closeSync(fd);
147668
+ fd = fs71.openSync(command, "r");
147669
+ fs71.readSync(fd, buffer, 0, size, 0);
147670
+ fs71.closeSync(fd);
147042
147671
  } catch (e) {
147043
147672
  }
147044
147673
  return shebangCommand(buffer.toString());
@@ -147442,7 +148071,7 @@ var require_package = __commonJS({
147442
148071
  // node_modules/.pnpm/dotenv@16.6.1/node_modules/dotenv/lib/main.js
147443
148072
  var require_main = __commonJS({
147444
148073
  "node_modules/.pnpm/dotenv@16.6.1/node_modules/dotenv/lib/main.js"(exports, module) {
147445
- var fs70 = __require("fs");
148074
+ var fs71 = __require("fs");
147446
148075
  var path80 = __require("path");
147447
148076
  var os15 = __require("os");
147448
148077
  var crypto6 = __require("crypto");
@@ -147551,7 +148180,7 @@ var require_main = __commonJS({
147551
148180
  if (options && options.path && options.path.length > 0) {
147552
148181
  if (Array.isArray(options.path)) {
147553
148182
  for (const filepath of options.path) {
147554
- if (fs70.existsSync(filepath)) {
148183
+ if (fs71.existsSync(filepath)) {
147555
148184
  possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
147556
148185
  }
147557
148186
  }
@@ -147561,7 +148190,7 @@ var require_main = __commonJS({
147561
148190
  } else {
147562
148191
  possibleVaultPath = path80.resolve(process.cwd(), ".env.vault");
147563
148192
  }
147564
- if (fs70.existsSync(possibleVaultPath)) {
148193
+ if (fs71.existsSync(possibleVaultPath)) {
147565
148194
  return possibleVaultPath;
147566
148195
  }
147567
148196
  return null;
@@ -147610,7 +148239,7 @@ var require_main = __commonJS({
147610
148239
  const parsedAll = {};
147611
148240
  for (const path81 of optionPaths) {
147612
148241
  try {
147613
- const parsed = DotenvModule.parse(fs70.readFileSync(path81, { encoding }));
148242
+ const parsed = DotenvModule.parse(fs71.readFileSync(path81, { encoding }));
147614
148243
  DotenvModule.populate(parsedAll, parsed, options);
147615
148244
  } catch (e) {
147616
148245
  if (debug2) {
@@ -148446,7 +149075,7 @@ var init_schemas2 = __esm({
148446
149075
  // packages/core/dist/services/llm/cli-provider.js
148447
149076
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
148448
149077
  import { join as join10 } from "node:path";
148449
- import { tmpdir } from "node:os";
149078
+ import { tmpdir as tmpdir2 } from "node:os";
148450
149079
  import { randomUUID as randomUUID4 } from "node:crypto";
148451
149080
  var import_cross_spawn, BaseCLIProvider, ClaudeCodeProvider;
148452
149081
  var init_cli_provider = __esm({
@@ -148516,7 +149145,7 @@ var init_cli_provider = __esm({
148516
149145
  constructor(transport) {
148517
149146
  this.transport = transport;
148518
149147
  if (process.env.TRUECOURSE_CLI_DEBUG) {
148519
- this.debugDir = join10(tmpdir(), "truecourse-cli-debug");
149148
+ this.debugDir = join10(tmpdir2(), "truecourse-cli-debug");
148520
149149
  mkdirSync2(this.debugDir, { recursive: true });
148521
149150
  log.info(`[CLI] Debug output: ${this.debugDir}`);
148522
149151
  }
@@ -148629,8 +149258,13 @@ var init_cli_provider = __esm({
148629
149258
  unregisterChildProcess(this._repoId, child);
148630
149259
  reject(new Error(`[CLI] Failed to spawn ${this.binaryName}: ${err.message}`));
148631
149260
  });
148632
- child.stdin.write(prompt);
148633
- child.stdin.end();
149261
+ child.stdin.on("error", () => {
149262
+ });
149263
+ try {
149264
+ child.stdin.write(prompt);
149265
+ child.stdin.end();
149266
+ } catch {
149267
+ }
148634
149268
  });
148635
149269
  }
148636
149270
  /** Extract usage data from CLI JSON envelope if present. */
@@ -149191,7 +149825,7 @@ var init_provider = __esm({
149191
149825
  });
149192
149826
 
149193
149827
  // packages/core/dist/services/llm/context-router.js
149194
- function isTestFile2(filePath) {
149828
+ function isTestFile3(filePath) {
149195
149829
  return TEST_PATTERNS.some((p2) => filePath.includes(p2));
149196
149830
  }
149197
149831
  function matchesFileFilter(fa, filter2, fileContent) {
@@ -149226,7 +149860,7 @@ function matchesFileFilter(fa, filter2, fileContent) {
149226
149860
  return false;
149227
149861
  }
149228
149862
  if (filter2.isTestFile !== void 0) {
149229
- if (filter2.isTestFile !== isTestFile2(fa.filePath))
149863
+ if (filter2.isTestFile !== isTestFile3(fa.filePath))
149230
149864
  return false;
149231
149865
  }
149232
149866
  if (filter2.languages) {
@@ -150043,7 +150677,7 @@ function findCSharpProjectFiles(repoPath) {
150043
150677
  continue;
150044
150678
  walk20(path16.join(dir, e.name), depth + 1);
150045
150679
  } else if (e.isFile()) {
150046
- if (e.name.endsWith(".sln"))
150680
+ if (e.name.endsWith(".sln") || e.name.endsWith(".slnx"))
150047
150681
  solutions.push(path16.join(dir, e.name));
150048
150682
  else if (e.name.endsWith(".csproj"))
150049
150683
  projects.push(path16.join(dir, e.name));
@@ -150306,6 +150940,29 @@ async function runViolationPipeline(input) {
150306
150940
  }
150307
150941
  }
150308
150942
  }
150943
+ {
150944
+ const detByDomain = /* @__PURE__ */ new Map();
150945
+ for (const v of allCodeViolations) {
150946
+ const d3 = v.ruleKey.split("/")[0];
150947
+ detByDomain.set(d3, (detByDomain.get(d3) ?? 0) + 1);
150948
+ }
150949
+ const archDet = serviceViolationResults.length + moduleViolationResults.length + methodViolationResults.length;
150950
+ if (archDet > 0)
150951
+ detByDomain.set("architecture", (detByDomain.get("architecture") ?? 0) + archDet);
150952
+ for (const domain of DOMAIN_ORDER) {
150953
+ const count = detByDomain.get(domain) ?? 0;
150954
+ tracker?.done(domain, count > 0 ? `${count} violations` : "Clean");
150955
+ }
150956
+ }
150957
+ const detViolationCount = allCodeViolations.length;
150958
+ let csharpStepActive = false;
150959
+ const startCsharpStep = (detail) => {
150960
+ if (!csharpStepActive) {
150961
+ tracker?.ensureStep("csharp", "C# semantic analysis");
150962
+ csharpStepActive = true;
150963
+ }
150964
+ tracker?.start("csharp", detail);
150965
+ };
150309
150966
  const enabledHostRules = enabledCodeRules.filter((r) => r.engine === "roslyn-host");
150310
150967
  if (enabledHostRules.length > 0) {
150311
150968
  const csharpFiles = [];
@@ -150319,6 +150976,7 @@ async function runViolationPipeline(input) {
150319
150976
  csharpFiles.push({ path: key4, text: fc.content });
150320
150977
  }
150321
150978
  if (csharpFiles.length > 0) {
150979
+ startCsharpStep(`Analyzing ${csharpFiles.length} C# files\u2026`);
150322
150980
  const ruleByKey = new Map(enabledHostRules.map((r) => [r.key, r]));
150323
150981
  const hostViolations = await runRoslynHost(csharpFiles, enabledHostRules.map((r) => r.key));
150324
150982
  for (const v of hostViolations) {
@@ -150345,19 +151003,20 @@ async function runViolationPipeline(input) {
150345
151003
  if (enabledWorkspaceRules.length > 0 && filesToScan.some((f2) => detectLanguage(f2.filePath) === "csharp")) {
150346
151004
  const projectFiles = findCSharpProjectFiles(repoPath);
150347
151005
  if (projectFiles.length === 0) {
150348
- log.info("[Pipeline] C# present but no .csproj/.sln found \u2014 project-aware C# rules skipped");
151006
+ log.info("[Pipeline] C# present but no .csproj/.sln/.slnx found \u2014 project-aware C# rules skipped");
150349
151007
  } else {
150350
151008
  const ruleByKey = new Map(enabledWorkspaceRules.map((r) => [r.key, r]));
150351
151009
  const ruleKeys = enabledWorkspaceRules.map((r) => r.key);
150352
- for (const projectFile of projectFiles) {
151010
+ for (const [pi, projectFile] of projectFiles.entries()) {
151011
+ startCsharpStep(projectFiles.length > 1 ? `Loading project ${pi + 1}/${projectFiles.length}: ${path16.basename(projectFile)}\u2026` : `Loading project ${path16.basename(projectFile)}\u2026`);
150353
151012
  let wsViolations;
150354
151013
  try {
150355
151014
  wsViolations = await runRoslynWorkspace(projectFile, ruleKeys);
150356
151015
  } catch (err) {
151016
+ tracker?.error("csharp", `Project load failed: ${path16.basename(projectFile)}`);
150357
151017
  if (err instanceof RoslynHostUnavailableError)
150358
151018
  throw err;
150359
- log.warn(`[Pipeline] Project-aware C# rules skipped for ${path16.basename(projectFile)}: ${err.message}. Restore the project (\`dotnet restore\`) for full C# coverage.`);
150360
- continue;
151019
+ throw new Error(`Project-aware C# analysis failed for ${path16.basename(projectFile)}: ${err.message}. Restore the project (\`dotnet restore\`) and ensure its pinned .NET SDK is installed.`);
150361
151020
  }
150362
151021
  for (const v of wsViolations) {
150363
151022
  const rule = ruleByKey.get(v.ruleKey);
@@ -150389,6 +151048,10 @@ async function runViolationPipeline(input) {
150389
151048
  }
150390
151049
  }
150391
151050
  }
151051
+ if (csharpStepActive) {
151052
+ const csharpFindings = allCodeViolations.length - detViolationCount;
151053
+ tracker?.done("csharp", csharpFindings > 0 ? `${csharpFindings} violations` : "Clean");
151054
+ }
150392
151055
  log.info(`[Pipeline] Code scan: ${allCodeViolations.length} violations from ${filesToScan.length} files (${enabledCodeRules.length} det rules, ${enabledLlmCodeRules.length} LLM rules)`);
150393
151056
  if (enabledCodeRules.some((r) => r.key === "bugs/deterministic/invalid-pyproject-toml")) {
150394
151057
  const pyprojectPath = path16.join(repoPath, "pyproject.toml");
@@ -150434,10 +151097,6 @@ async function runViolationPipeline(input) {
150434
151097
  }
150435
151098
  const totalDet = [...violationsByDomain.values()].reduce((a, b) => a + b, 0);
150436
151099
  log.info(`[Pipeline] Totals: ${DOMAIN_ORDER.filter((d3) => violationsByDomain.has(d3)).map((d3) => `${d3}=${violationsByDomain.get(d3)}`).join(", ")} (${totalDet})`);
150437
- for (const domain of DOMAIN_ORDER) {
150438
- const count = violationsByDomain.get(domain) ?? 0;
150439
- tracker?.done(domain, count > 0 ? `${count} violations` : "Clean");
150440
- }
150441
151100
  onProgress?.({ step: "analyzing", percent: 84, detail: "Code checks done" });
150442
151101
  const prevLlmCodeByFile = /* @__PURE__ */ new Map();
150443
151102
  for (const cv of previousActiveCodeViolations) {
@@ -161703,7 +162362,7 @@ var require_form_data = __commonJS({
161703
162362
  var http = __require("http");
161704
162363
  var https = __require("https");
161705
162364
  var parseUrl = __require("url").parse;
161706
- var fs70 = __require("fs");
162365
+ var fs71 = __require("fs");
161707
162366
  var Stream = __require("stream").Stream;
161708
162367
  var crypto6 = __require("crypto");
161709
162368
  var mime = require_mime_types();
@@ -161770,7 +162429,7 @@ var require_form_data = __commonJS({
161770
162429
  if (value.end != void 0 && value.end != Infinity && value.start != void 0) {
161771
162430
  callback(null, value.end + 1 - (value.start ? value.start : 0));
161772
162431
  } else {
161773
- fs70.stat(value.path, function(err, stat) {
162432
+ fs71.stat(value.path, function(err, stat) {
161774
162433
  if (err) {
161775
162434
  callback(err);
161776
162435
  return;
@@ -162388,8 +163047,8 @@ var require_node2 = __commonJS({
162388
163047
  }
162389
163048
  break;
162390
163049
  case "FILE":
162391
- var fs70 = __require("fs");
162392
- stream2 = new fs70.SyncWriteStream(fd2, { autoClose: false });
163050
+ var fs71 = __require("fs");
163051
+ stream2 = new fs71.SyncWriteStream(fd2, { autoClose: false });
162393
163052
  stream2._type = "fs";
162394
163053
  break;
162395
163054
  case "PIPE":
@@ -167488,21 +168147,21 @@ function node(getModule) {
167488
168147
  if (lineMatch) {
167489
168148
  let object;
167490
168149
  let method;
167491
- let functionName;
168150
+ let functionName2;
167492
168151
  let typeName2;
167493
168152
  let methodName3;
167494
168153
  if (lineMatch[1]) {
167495
- functionName = lineMatch[1];
167496
- let methodStart = functionName.lastIndexOf(".");
167497
- if (functionName[methodStart - 1] === ".") {
168154
+ functionName2 = lineMatch[1];
168155
+ let methodStart = functionName2.lastIndexOf(".");
168156
+ if (functionName2[methodStart - 1] === ".") {
167498
168157
  methodStart--;
167499
168158
  }
167500
168159
  if (methodStart > 0) {
167501
- object = functionName.slice(0, methodStart);
167502
- method = functionName.slice(methodStart + 1);
168160
+ object = functionName2.slice(0, methodStart);
168161
+ method = functionName2.slice(methodStart + 1);
167503
168162
  const objectEnd = object.indexOf(".Module");
167504
168163
  if (objectEnd > 0) {
167505
- functionName = functionName.slice(objectEnd + 1);
168164
+ functionName2 = functionName2.slice(objectEnd + 1);
167506
168165
  object = object.slice(0, objectEnd);
167507
168166
  }
167508
168167
  }
@@ -167514,11 +168173,11 @@ function node(getModule) {
167514
168173
  }
167515
168174
  if (method === "<anonymous>") {
167516
168175
  methodName3 = void 0;
167517
- functionName = void 0;
168176
+ functionName2 = void 0;
167518
168177
  }
167519
- if (functionName === void 0) {
168178
+ if (functionName2 === void 0) {
167520
168179
  methodName3 = methodName3 || UNKNOWN_FUNCTION;
167521
- functionName = typeName2 ? `${typeName2}.${methodName3}` : methodName3;
168180
+ functionName2 = typeName2 ? `${typeName2}.${methodName3}` : methodName3;
167522
168181
  }
167523
168182
  let filename = lineMatch[2]?.startsWith("file://") ? lineMatch[2].slice(7) : lineMatch[2];
167524
168183
  const isNative = lineMatch[5] === "native";
@@ -167531,7 +168190,7 @@ function node(getModule) {
167531
168190
  return {
167532
168191
  filename: filename ? decodeURI(filename) : void 0,
167533
168192
  module: getModule ? getModule(filename) : void 0,
167534
- function: functionName,
168193
+ function: functionName2,
167535
168194
  lineno: _parseIntOrUndefined(lineMatch[3]),
167536
168195
  colno: _parseIntOrUndefined(lineMatch[4]),
167537
168196
  in_app: filenameIsInApp(filename || "", isNative),
@@ -170369,7 +171028,7 @@ function readToolVersion() {
170369
171028
  if (cachedVersion)
170370
171029
  return cachedVersion;
170371
171030
  if (true) {
170372
- cachedVersion = "0.7.0-next.4";
171031
+ cachedVersion = "0.7.0-next.5";
170373
171032
  return cachedVersion;
170374
171033
  }
170375
171034
  try {
@@ -172775,13 +173434,13 @@ var init_pexprs_toFailure = __esm({
172775
173434
  return new Failure(this, "a Unicode [" + this.categoryOrProp + "] character", "description");
172776
173435
  };
172777
173436
  Alt.prototype.toFailure = function(grammar2) {
172778
- const fs70 = this.terms.map((t2) => t2.toFailure(grammar2));
172779
- const description = "(" + fs70.join(" or ") + ")";
173437
+ const fs71 = this.terms.map((t2) => t2.toFailure(grammar2));
173438
+ const description = "(" + fs71.join(" or ") + ")";
172780
173439
  return new Failure(this, description, "description");
172781
173440
  };
172782
173441
  Seq.prototype.toFailure = function(grammar2) {
172783
- const fs70 = this.factors.map((f2) => f2.toFailure(grammar2));
172784
- const description = "(" + fs70.join(" ") + ")";
173442
+ const fs71 = this.factors.map((f2) => f2.toFailure(grammar2));
173443
+ const description = "(" + fs71.join(" ") + ")";
172785
173444
  return new Failure(this, description, "description");
172786
173445
  };
172787
173446
  Iter.prototype.toFailure = function(grammar2) {
@@ -174618,9 +175277,9 @@ function buildGrammar2(match4, namespace, optOhmGrammarForTesting, options) {
174618
175277
  decl.withSuperGrammar(namespace[superGrammarName]);
174619
175278
  }
174620
175279
  },
174621
- Rule_define(n, fs70, d3, _2, b) {
175280
+ Rule_define(n, fs71, d3, _2, b) {
174622
175281
  currentRuleName = n.visit();
174623
- currentRuleFormals = fs70.children.map((c2) => c2.visit())[0] || [];
175282
+ currentRuleFormals = fs71.children.map((c2) => c2.visit())[0] || [];
174624
175283
  if (!decl.defaultStartRule && decl.ensureSuperGrammar() !== Grammar.ProtoBuiltInRules) {
174625
175284
  decl.withDefaultStartRule(currentRuleName);
174626
175285
  }
@@ -174629,9 +175288,9 @@ function buildGrammar2(match4, namespace, optOhmGrammarForTesting, options) {
174629
175288
  const source = this.source.trimmed();
174630
175289
  return decl.define(currentRuleName, currentRuleFormals, body, description, source);
174631
175290
  },
174632
- Rule_override(n, fs70, _2, b) {
175291
+ Rule_override(n, fs71, _2, b) {
174633
175292
  currentRuleName = n.visit();
174634
- currentRuleFormals = fs70.children.map((c2) => c2.visit())[0] || [];
175293
+ currentRuleFormals = fs71.children.map((c2) => c2.visit())[0] || [];
174635
175294
  const source = this.source.trimmed();
174636
175295
  decl.ensureSuperGrammarRuleForOverriding(currentRuleName, source);
174637
175296
  overriding = true;
@@ -174639,9 +175298,9 @@ function buildGrammar2(match4, namespace, optOhmGrammarForTesting, options) {
174639
175298
  overriding = false;
174640
175299
  return decl.override(currentRuleName, currentRuleFormals, body, null, source);
174641
175300
  },
174642
- Rule_extend(n, fs70, _2, b) {
175301
+ Rule_extend(n, fs71, _2, b) {
174643
175302
  currentRuleName = n.visit();
174644
- currentRuleFormals = fs70.children.map((c2) => c2.visit())[0] || [];
175303
+ currentRuleFormals = fs71.children.map((c2) => c2.visit())[0] || [];
174645
175304
  const body = b.visit();
174646
175305
  const source = this.source.trimmed();
174647
175306
  return decl.extend(currentRuleName, currentRuleFormals, body, null, source);
@@ -174668,8 +175327,8 @@ function buildGrammar2(match4, namespace, optOhmGrammarForTesting, options) {
174668
175327
  return builder.alt(...args).withSource(this.source);
174669
175328
  }
174670
175329
  },
174671
- Formals(opointy, fs70, cpointy) {
174672
- return fs70.visit();
175330
+ Formals(opointy, fs71, cpointy) {
175331
+ return fs71.visit();
174673
175332
  },
174674
175333
  Params(opointy, ps, cpointy) {
174675
175334
  return ps.visit();
@@ -174831,8 +175490,8 @@ function initPrototypeParser(grammar2) {
174831
175490
  formals: optFormals.children.map((c2) => c2.parse())[0] || []
174832
175491
  };
174833
175492
  },
174834
- Formals(oparen, fs70, cparen) {
174835
- return fs70.asIteration().children.map((c2) => c2.parse());
175493
+ Formals(oparen, fs71, cparen) {
175494
+ return fs71.asIteration().children.map((c2) => c2.parse());
174836
175495
  },
174837
175496
  name(first2, rest) {
174838
175497
  return this.sourceString;
@@ -178280,9 +178939,9 @@ var init_conformance = __esm({
178280
178939
  });
178281
178940
 
178282
178941
  // packages/contract-verifier/dist/extractor/source-walker.js
178283
- import fs30 from "node:fs";
178942
+ import fs31 from "node:fs";
178284
178943
  import path33 from "node:path";
178285
- function isTestFile3(filePath) {
178944
+ function isTestFile4(filePath) {
178286
178945
  const parts = filePath.replace(/\\/g, "/").split("/");
178287
178946
  const fileName = parts[parts.length - 1] ?? "";
178288
178947
  if (/\.(test|spec)\.[mc]?[jt]sx?$/.test(fileName))
@@ -178299,7 +178958,7 @@ async function eachParsedSource(rootDir, visit) {
178299
178958
  const walk20 = (dir) => {
178300
178959
  let entries;
178301
178960
  try {
178302
- entries = fs30.readdirSync(dir, { withFileTypes: true });
178961
+ entries = fs31.readdirSync(dir, { withFileTypes: true });
178303
178962
  } catch {
178304
178963
  return;
178305
178964
  }
@@ -178315,14 +178974,14 @@ async function eachParsedSource(rootDir, visit) {
178315
178974
  }
178316
178975
  if (!entry.isFile())
178317
178976
  continue;
178318
- if (isTestFile3(full))
178977
+ if (isTestFile4(full))
178319
178978
  continue;
178320
178979
  const lang = EXT_TO_LANG[path33.extname(entry.name)];
178321
178980
  if (!lang)
178322
178981
  continue;
178323
178982
  let source;
178324
178983
  try {
178325
- source = fs30.readFileSync(full, "utf-8");
178984
+ source = fs31.readFileSync(full, "utf-8");
178326
178985
  } catch {
178327
178986
  continue;
178328
178987
  }
@@ -178433,19 +179092,19 @@ function readCsharpString(node2, source) {
178433
179092
  }
178434
179093
  if (node2.type === "interpolated_string_expression") {
178435
179094
  let text4 = "";
178436
- let hasInterpolation2 = false;
179095
+ let hasInterpolation = false;
178437
179096
  for (let i = 0; i < node2.namedChildCount; i++) {
178438
179097
  const c2 = node2.namedChild(i);
178439
179098
  if (!c2)
178440
179099
  continue;
178441
179100
  if (c2.type === "interpolation") {
178442
179101
  text4 += " /*INTERP*/ ";
178443
- hasInterpolation2 = true;
179102
+ hasInterpolation = true;
178444
179103
  } else {
178445
179104
  text4 += source.slice(c2.startIndex, c2.endIndex);
178446
179105
  }
178447
179106
  }
178448
- return { text: text4, hasInterpolation: hasInterpolation2 };
179107
+ return { text: text4, hasInterpolation };
178449
179108
  }
178450
179109
  if (node2.type === "binary_expression") {
178451
179110
  const op = node2.childForFieldName("operator");
@@ -180410,7 +181069,7 @@ var init_operation_aspnet = __esm({
180410
181069
  });
180411
181070
 
180412
181071
  // packages/contract-verifier/dist/extractor/file-based-routes.js
180413
- import fs31 from "node:fs";
181072
+ import fs32 from "node:fs";
180414
181073
  import path34 from "node:path";
180415
181074
  async function extractFileBasedRoutesFromDir(rootDir) {
180416
181075
  await initParsers();
@@ -180429,7 +181088,7 @@ function findDirs(rootDir, wantedRel) {
180429
181088
  const visit = (dir) => {
180430
181089
  let entries;
180431
181090
  try {
180432
- entries = fs31.readdirSync(dir, { withFileTypes: true });
181091
+ entries = fs32.readdirSync(dir, { withFileTypes: true });
180433
181092
  } catch {
180434
181093
  return;
180435
181094
  }
@@ -180455,7 +181114,7 @@ function findDirs(rootDir, wantedRel) {
180455
181114
  function walkRoot(rootAbs, root2, out) {
180456
181115
  const tcIgnore = loadTcIgnore(rootAbs);
180457
181116
  const visit = (dir) => {
180458
- for (const entry of fs31.readdirSync(dir, { withFileTypes: true })) {
181117
+ for (const entry of fs32.readdirSync(dir, { withFileTypes: true })) {
180459
181118
  if (entry.name === "node_modules" || entry.name === ".git")
180460
181119
  continue;
180461
181120
  const full = path34.join(dir, entry.name);
@@ -180480,7 +181139,7 @@ function walkRoot(rootAbs, root2, out) {
180480
181139
  const url = deriveUrl(relUnderRoot, root2);
180481
181140
  if (url === null)
180482
181141
  continue;
180483
- const source = fs31.readFileSync(full, "utf-8");
181142
+ const source = fs32.readFileSync(full, "utf-8");
180484
181143
  const lang = ext2 === ".tsx" ? "tsx" : ext2 === ".ts" ? "typescript" : "javascript";
180485
181144
  let tree;
180486
181145
  try {
@@ -180620,7 +181279,7 @@ var init_file_based_routes = __esm({
180620
181279
  });
180621
181280
 
180622
181281
  // packages/contract-verifier/dist/extractor/mount-graph.js
180623
- import fs32 from "node:fs";
181282
+ import fs33 from "node:fs";
180624
181283
  import path35 from "node:path";
180625
181284
  function analyzeRouterFile(filePath, source, tree) {
180626
181285
  const declarations = [];
@@ -180771,7 +181430,7 @@ function resolveImportPath(fromFile, spec) {
180771
181430
  ];
180772
181431
  for (const c2 of candidates) {
180773
181432
  const abs = path35.resolve(baseDir, c2);
180774
- if (fs32.existsSync(abs) && fs32.statSync(abs).isFile())
181433
+ if (fs33.existsSync(abs) && fs33.statSync(abs).isFile())
180775
181434
  return abs;
180776
181435
  }
180777
181436
  return null;
@@ -180956,7 +181615,7 @@ var init_mount_graph = __esm({
180956
181615
  });
180957
181616
 
180958
181617
  // packages/contract-verifier/dist/extractor/auth-presence.js
180959
- import fs33 from "node:fs";
181618
+ import fs34 from "node:fs";
180960
181619
  import path36 from "node:path";
180961
181620
  async function detectAuthPresence(rootDir) {
180962
181621
  await initParsers();
@@ -180967,7 +181626,7 @@ async function detectAuthPresence(rootDir) {
180967
181626
  const scanned = [];
180968
181627
  const tcIgnore = loadTcIgnore(rootDir);
180969
181628
  const visit = (dir) => {
180970
- for (const entry of fs33.readdirSync(dir, { withFileTypes: true })) {
181629
+ for (const entry of fs34.readdirSync(dir, { withFileTypes: true })) {
180971
181630
  if (entry.name === "node_modules" || entry.name === ".git")
180972
181631
  continue;
180973
181632
  const full = path36.join(dir, entry.name);
@@ -180982,7 +181641,7 @@ async function detectAuthPresence(rootDir) {
180982
181641
  const ext2 = path36.extname(entry.name);
180983
181642
  if (!TS_EXT2.has(ext2))
180984
181643
  continue;
180985
- const source = fs33.readFileSync(full, "utf-8");
181644
+ const source = fs34.readFileSync(full, "utf-8");
180986
181645
  const lang = ext2 === ".tsx" ? "tsx" : ext2 === ".ts" ? "typescript" : "javascript";
180987
181646
  let tree;
180988
181647
  try {
@@ -181075,7 +181734,7 @@ function resolveImportPath2(importingFile, sourceStr) {
181075
181734
  candidates.push(path36.join(baseRaw, `index${ext2}`));
181076
181735
  }
181077
181736
  for (const c2 of candidates) {
181078
- if (fs33.existsSync(c2) && fs33.statSync(c2).isFile())
181737
+ if (fs34.existsSync(c2) && fs34.statSync(c2).isFile())
181079
181738
  return c2;
181080
181739
  }
181081
181740
  return null;
@@ -181167,7 +181826,7 @@ var init_auth_presence = __esm({
181167
181826
  });
181168
181827
 
181169
181828
  // packages/contract-verifier/dist/extractor/idempotency-presence.js
181170
- import fs34 from "node:fs";
181829
+ import fs35 from "node:fs";
181171
181830
  import path37 from "node:path";
181172
181831
  function routeKey(filePath, declarationLine) {
181173
181832
  return `${filePath}::${declarationLine}`;
@@ -181180,7 +181839,7 @@ async function detectIdempotencyPresence(rootDir, requestHeader) {
181180
181839
  const scanned = [];
181181
181840
  const tcIgnore = loadTcIgnore(rootDir);
181182
181841
  const visit = (dir) => {
181183
- for (const entry of fs34.readdirSync(dir, { withFileTypes: true })) {
181842
+ for (const entry of fs35.readdirSync(dir, { withFileTypes: true })) {
181184
181843
  if (entry.name === "node_modules" || entry.name === ".git")
181185
181844
  continue;
181186
181845
  const full = path37.join(dir, entry.name);
@@ -181195,7 +181854,7 @@ async function detectIdempotencyPresence(rootDir, requestHeader) {
181195
181854
  const ext2 = path37.extname(entry.name);
181196
181855
  if (!TS_EXT3.has(ext2))
181197
181856
  continue;
181198
- const source = fs34.readFileSync(full, "utf-8");
181857
+ const source = fs35.readFileSync(full, "utf-8");
181199
181858
  const lang = ext2 === ".tsx" ? "tsx" : ext2 === ".ts" ? "typescript" : "javascript";
181200
181859
  let tree;
181201
181860
  try {
@@ -181426,7 +182085,7 @@ function resolveImportPath3(importingFile, sourceStr) {
181426
182085
  candidates.push(path37.join(baseRaw, `index${ext2}`));
181427
182086
  }
181428
182087
  for (const c2 of candidates) {
181429
- if (fs34.existsSync(c2) && fs34.statSync(c2).isFile())
182088
+ if (fs35.existsSync(c2) && fs35.statSync(c2).isFile())
181430
182089
  return c2;
181431
182090
  }
181432
182091
  return null;
@@ -182448,14 +183107,14 @@ function getSqlText(node2, source) {
182448
183107
  return { text: source.slice(node2.startIndex, node2.endIndex), hasInterpolation: false };
182449
183108
  }
182450
183109
  function extractFromSqlSource(sqlNode, filePath, source) {
182451
- const { text: text4, hasInterpolation: hasInterpolation2 } = getSqlText(sqlNode, source);
182452
- return buildQueriesFromSqlText(text4, hasInterpolation2, {
183110
+ const { text: text4, hasInterpolation } = getSqlText(sqlNode, source);
183111
+ return buildQueriesFromSqlText(text4, hasInterpolation, {
182453
183112
  filePath,
182454
183113
  lineStart: sqlNode.startPosition.row + 1,
182455
183114
  lineEnd: sqlNode.endPosition.row + 1
182456
183115
  });
182457
183116
  }
182458
- function buildQueriesFromSqlText(text4, hasInterpolation2, loc, adapterName = "raw-sql") {
183117
+ function buildQueriesFromSqlText(text4, hasInterpolation, loc, adapterName = "raw-sql") {
182459
183118
  if (!text4 || !/\bSELECT\b/i.test(text4))
182460
183119
  return [];
182461
183120
  const blocks = splitSelectBlocks(text4);
@@ -182474,7 +183133,7 @@ function buildQueriesFromSqlText(text4, hasInterpolation2, loc, adapterName = "r
182474
183133
  else
182475
183134
  unparseable.push({ reason: "unrecognised SQL predicate", raw: piece });
182476
183135
  }
182477
- if (hasInterpolation2) {
183136
+ if (hasInterpolation) {
182478
183137
  unparseable.push({ reason: "string-interpolation substitution skipped", raw: "${...}" });
182479
183138
  }
182480
183139
  out.push({
@@ -182836,7 +183495,7 @@ function pySqlText(node2, source) {
182836
183495
  function readPyStringNode(node2, source) {
182837
183496
  if (node2.type === "concatenated_string") {
182838
183497
  let text5 = "";
182839
- let hasInterpolation3 = false;
183498
+ let hasInterpolation2 = false;
182840
183499
  for (let i = 0; i < node2.namedChildCount; i++) {
182841
183500
  const c2 = node2.namedChild(i);
182842
183501
  if (!c2)
@@ -182844,15 +183503,15 @@ function readPyStringNode(node2, source) {
182844
183503
  const part = readPyStringNode(c2, source);
182845
183504
  if (part) {
182846
183505
  text5 += part.text;
182847
- hasInterpolation3 = hasInterpolation3 || part.hasInterpolation;
183506
+ hasInterpolation2 = hasInterpolation2 || part.hasInterpolation;
182848
183507
  }
182849
183508
  }
182850
- return { text: text5, hasInterpolation: hasInterpolation3 };
183509
+ return { text: text5, hasInterpolation: hasInterpolation2 };
182851
183510
  }
182852
183511
  if (node2.type !== "string")
182853
183512
  return null;
182854
183513
  let text4 = "";
182855
- let hasInterpolation2 = false;
183514
+ let hasInterpolation = false;
182856
183515
  for (let i = 0; i < node2.namedChildCount; i++) {
182857
183516
  const c2 = node2.namedChild(i);
182858
183517
  if (!c2)
@@ -182861,10 +183520,10 @@ function readPyStringNode(node2, source) {
182861
183520
  text4 += source.slice(c2.startIndex, c2.endIndex);
182862
183521
  else if (c2.type === "interpolation") {
182863
183522
  text4 += " /*INTERP*/ ";
182864
- hasInterpolation2 = true;
183523
+ hasInterpolation = true;
182865
183524
  }
182866
183525
  }
182867
- return { text: text4, hasInterpolation: hasInterpolation2 };
183526
+ return { text: text4, hasInterpolation };
182868
183527
  }
182869
183528
  var SQL_HOST_METHODS, SQL_TAG_NAMES, COL, VAL, PY_SQL_EXEC_METHODS;
182870
183529
  var init_raw_sql = __esm({
@@ -184811,7 +185470,7 @@ var init_minimatch = __esm({
184811
185470
  });
184812
185471
 
184813
185472
  // packages/contract-verifier/dist/extractor/manifests.js
184814
- import fs35 from "node:fs";
185473
+ import fs36 from "node:fs";
184815
185474
  import path39 from "node:path";
184816
185475
  function collectDependencies(rootDir) {
184817
185476
  const out = [];
@@ -184819,7 +185478,7 @@ function collectDependencies(rootDir) {
184819
185478
  const visit = (dir) => {
184820
185479
  let entries;
184821
185480
  try {
184822
- entries = fs35.readdirSync(dir, { withFileTypes: true });
185481
+ entries = fs36.readdirSync(dir, { withFileTypes: true });
184823
185482
  } catch {
184824
185483
  return;
184825
185484
  }
@@ -184857,7 +185516,7 @@ function collectDependencies(rootDir) {
184857
185516
  function readPackageJson(filePath) {
184858
185517
  let pkg;
184859
185518
  try {
184860
- pkg = JSON.parse(fs35.readFileSync(filePath, "utf-8"));
185519
+ pkg = JSON.parse(fs36.readFileSync(filePath, "utf-8"));
184861
185520
  } catch {
184862
185521
  return [];
184863
185522
  }
@@ -184882,7 +185541,7 @@ function pyDistName(spec) {
184882
185541
  function readRequirements(filePath) {
184883
185542
  let text4;
184884
185543
  try {
184885
- text4 = fs35.readFileSync(filePath, "utf-8");
185544
+ text4 = fs36.readFileSync(filePath, "utf-8");
184886
185545
  } catch {
184887
185546
  return [];
184888
185547
  }
@@ -184897,7 +185556,7 @@ function readRequirements(filePath) {
184897
185556
  function readPyproject(filePath) {
184898
185557
  let text4;
184899
185558
  try {
184900
- text4 = fs35.readFileSync(filePath, "utf-8");
185559
+ text4 = fs36.readFileSync(filePath, "utf-8");
184901
185560
  } catch {
184902
185561
  return [];
184903
185562
  }
@@ -184922,7 +185581,7 @@ function readPyproject(filePath) {
184922
185581
  function readSetupPy(filePath) {
184923
185582
  let text4;
184924
185583
  try {
184925
- text4 = fs35.readFileSync(filePath, "utf-8");
185584
+ text4 = fs36.readFileSync(filePath, "utf-8");
184926
185585
  } catch {
184927
185586
  return [];
184928
185587
  }
@@ -184940,7 +185599,7 @@ function readSetupPy(filePath) {
184940
185599
  function readXmlPackageTags(filePath, tag, idAttr, versionAttr, field) {
184941
185600
  let text4;
184942
185601
  try {
184943
- text4 = fs35.readFileSync(filePath, "utf-8");
185602
+ text4 = fs36.readFileSync(filePath, "utf-8");
184944
185603
  } catch {
184945
185604
  return [];
184946
185605
  }
@@ -184992,7 +185651,7 @@ var init_manifests = __esm({
184992
185651
  });
184993
185652
 
184994
185653
  // packages/contract-verifier/dist/extractor/forbidden/index.js
184995
- import fs36 from "node:fs";
185654
+ import fs37 from "node:fs";
184996
185655
  import path40 from "node:path";
184997
185656
  function detectForbiddenFiles(rootDir, pattern) {
184998
185657
  const out = [];
@@ -185000,7 +185659,7 @@ function detectForbiddenFiles(rootDir, pattern) {
185000
185659
  const visit = (dir) => {
185001
185660
  let entries;
185002
185661
  try {
185003
- entries = fs36.readdirSync(dir, { withFileTypes: true });
185662
+ entries = fs37.readdirSync(dir, { withFileTypes: true });
185004
185663
  } catch {
185005
185664
  return;
185006
185665
  }
@@ -185237,7 +185896,7 @@ function detectFlagInConfigFiles(rootDir, name) {
185237
185896
  const visit = (dir) => {
185238
185897
  let entries;
185239
185898
  try {
185240
- entries = fs36.readdirSync(dir, { withFileTypes: true });
185899
+ entries = fs37.readdirSync(dir, { withFileTypes: true });
185241
185900
  } catch {
185242
185901
  return;
185243
185902
  }
@@ -185256,7 +185915,7 @@ function detectFlagInConfigFiles(rootDir, name) {
185256
185915
  continue;
185257
185916
  let source;
185258
185917
  try {
185259
- source = fs36.readFileSync(full, "utf-8");
185918
+ source = fs37.readFileSync(full, "utf-8");
185260
185919
  } catch {
185261
185920
  continue;
185262
185921
  }
@@ -188315,7 +188974,7 @@ var init_emission_facts = __esm({
188315
188974
  });
188316
188975
 
188317
188976
  // packages/contract-verifier/dist/extractor/entity-schema/index.js
188318
- import fs37 from "node:fs";
188977
+ import fs38 from "node:fs";
188319
188978
  import path41 from "node:path";
188320
188979
  async function extractEntitiesFromDir(rootDir) {
188321
188980
  const tcIgnore = loadTcIgnore(rootDir);
@@ -188323,7 +188982,7 @@ async function extractEntitiesFromDir(rootDir) {
188323
188982
  const walk20 = (dir) => {
188324
188983
  let entries;
188325
188984
  try {
188326
- entries = fs37.readdirSync(dir, { withFileTypes: true });
188985
+ entries = fs38.readdirSync(dir, { withFileTypes: true });
188327
188986
  } catch {
188328
188987
  return;
188329
188988
  }
@@ -188336,7 +188995,7 @@ async function extractEntitiesFromDir(rootDir) {
188336
188995
  if (e.isDirectory())
188337
188996
  walk20(full);
188338
188997
  else if (e.isFile() && e.name.endsWith(".prisma")) {
188339
- out.push(...parsePrismaModels(full, fs37.readFileSync(full, "utf-8")));
188998
+ out.push(...parsePrismaModels(full, fs38.readFileSync(full, "utf-8")));
188340
188999
  }
188341
189000
  }
188342
189001
  };
@@ -189958,7 +190617,7 @@ var init_characteristic_imports = __esm({
189958
190617
  });
189959
190618
 
189960
190619
  // packages/contract-verifier/dist/extractor/architecture/shared/config-files.js
189961
- import fs38 from "node:fs";
190620
+ import fs39 from "node:fs";
189962
190621
  import path42 from "node:path";
189963
190622
  function collectFileIndex(rootDir) {
189964
190623
  const files = [];
@@ -189967,7 +190626,7 @@ function collectFileIndex(rootDir) {
189967
190626
  const visit = (dir) => {
189968
190627
  let entries;
189969
190628
  try {
189970
- entries = fs38.readdirSync(dir, { withFileTypes: true });
190629
+ entries = fs39.readdirSync(dir, { withFileTypes: true });
189971
190630
  } catch {
189972
190631
  return;
189973
190632
  }
@@ -189993,7 +190652,7 @@ function collectFileIndex(rootDir) {
189993
190652
  return cache3.get(relPath);
189994
190653
  let text4;
189995
190654
  try {
189996
- text4 = fs38.readFileSync(path42.join(rootDir, relPath), "utf-8");
190655
+ text4 = fs39.readFileSync(path42.join(rootDir, relPath), "utf-8");
189997
190656
  } catch {
189998
190657
  text4 = null;
189999
190658
  }
@@ -190450,9 +191109,9 @@ __export(extractor_exports, {
190450
191109
  extractValidationRulesFromDir: () => extractValidationRulesFromDir,
190451
191110
  getArchitectureDetector: () => getArchitectureDetector
190452
191111
  });
190453
- import fs39 from "node:fs";
191112
+ import fs40 from "node:fs";
190454
191113
  import path44 from "node:path";
190455
- function isTestFile4(filePath) {
191114
+ function isTestFile5(filePath) {
190456
191115
  const parts = filePath.replace(/\\/g, "/").split("/");
190457
191116
  const fileName = parts[parts.length - 1];
190458
191117
  if (/\.(test|spec)\.(t|j)sx?$/.test(fileName))
@@ -190467,7 +191126,7 @@ async function extractOperationsFromDir(rootDir) {
190467
191126
  const fileAnalyses = [];
190468
191127
  const tcIgnore = loadTcIgnore(rootDir);
190469
191128
  const visit = (dir) => {
190470
- for (const entry of fs39.readdirSync(dir, { withFileTypes: true })) {
191129
+ for (const entry of fs40.readdirSync(dir, { withFileTypes: true })) {
190471
191130
  if (entry.name === "node_modules" || entry.name === ".git")
190472
191131
  continue;
190473
191132
  const full = path44.join(dir, entry.name);
@@ -190482,9 +191141,9 @@ async function extractOperationsFromDir(rootDir) {
190482
191141
  const ext2 = path44.extname(entry.name);
190483
191142
  if (!TS_EXT4.has(ext2))
190484
191143
  continue;
190485
- if (isTestFile4(full))
191144
+ if (isTestFile5(full))
190486
191145
  continue;
190487
- const source = fs39.readFileSync(full, "utf-8");
191146
+ const source = fs40.readFileSync(full, "utf-8");
190488
191147
  const lang = ext2 === ".ts" || ext2 === ".tsx" ? ext2 === ".tsx" ? "tsx" : "typescript" : "javascript";
190489
191148
  let tree;
190490
191149
  try {
@@ -192934,17 +193593,17 @@ var init_symbol_index = __esm({
192934
193593
  });
192935
193594
 
192936
193595
  // packages/contract-verifier/dist/verify.js
192937
- import fs40 from "node:fs";
193596
+ import fs41 from "node:fs";
192938
193597
  import path46 from "node:path";
192939
193598
  async function verify(opts) {
192940
193599
  const specFiles = [];
192941
193600
  walkTcFiles(opts.contractsDir, (filePath) => {
192942
- specFiles.push({ path: filePath, source: fs40.readFileSync(filePath, "utf-8") });
193601
+ specFiles.push({ path: filePath, source: fs41.readFileSync(filePath, "utf-8") });
192943
193602
  }, opts.includeInferred ?? false);
192944
193603
  const baseFiles = [];
192945
- if (opts.baseContractsDir && fs40.existsSync(opts.baseContractsDir)) {
193604
+ if (opts.baseContractsDir && fs41.existsSync(opts.baseContractsDir)) {
192946
193605
  walkTcFiles(opts.baseContractsDir, (filePath) => {
192947
- const source = fs40.readFileSync(filePath, "utf-8");
193606
+ const source = fs41.readFileSync(filePath, "utf-8");
192948
193607
  baseFiles.push(parseTcFile(filePath, source));
192949
193608
  }, opts.includeInferred ?? false);
192950
193609
  }
@@ -193334,7 +193993,7 @@ async function verify(opts) {
193334
193993
  };
193335
193994
  }
193336
193995
  function walkTcFiles(rootDir, visit, includeInferred) {
193337
- for (const entry of fs40.readdirSync(rootDir, { withFileTypes: true })) {
193996
+ for (const entry of fs41.readdirSync(rootDir, { withFileTypes: true })) {
193338
193997
  const full = path46.join(rootDir, entry.name);
193339
193998
  if (entry.isDirectory()) {
193340
193999
  if (!includeInferred && entry.name === "_inferred")
@@ -193629,7 +194288,7 @@ var init_serialize = __esm({
193629
194288
  });
193630
194289
 
193631
194290
  // packages/contract-verifier/dist/infer/index.js
193632
- import fs41 from "node:fs";
194291
+ import fs42 from "node:fs";
193633
194292
  import path48 from "node:path";
193634
194293
  async function infer(opts) {
193635
194294
  const authored = loadAuthored(opts.contractsDir);
@@ -193657,7 +194316,7 @@ async function infer(opts) {
193657
194316
  function loadAuthored(contractsDir) {
193658
194317
  const files = [];
193659
194318
  walkAuthoredTcFiles(contractsDir, (filePath) => {
193660
- files.push({ path: filePath, source: fs41.readFileSync(filePath, "utf-8") });
194319
+ files.push({ path: filePath, source: fs42.readFileSync(filePath, "utf-8") });
193661
194320
  });
193662
194321
  return parseAndResolve(files).index;
193663
194322
  }
@@ -194342,21 +195001,21 @@ function writeInferred(contractsDir, decisions, options = {}) {
194342
195001
  proposed.push(filePath);
194343
195002
  continue;
194344
195003
  }
194345
- fs41.mkdirSync(path48.dirname(filePath), { recursive: true });
194346
- const existing = fs41.existsSync(filePath) ? fs41.readFileSync(filePath, "utf-8") : null;
195004
+ fs42.mkdirSync(path48.dirname(filePath), { recursive: true });
195005
+ const existing = fs42.existsSync(filePath) ? fs42.readFileSync(filePath, "utf-8") : null;
194347
195006
  if (existing === r.tcSource)
194348
195007
  continue;
194349
- fs41.writeFileSync(filePath, r.tcSource);
195008
+ fs42.writeFileSync(filePath, r.tcSource);
194350
195009
  written.push(filePath);
194351
195010
  }
194352
- if (!options.dryRun && fs41.existsSync(root2))
195011
+ if (!options.dryRun && fs42.existsSync(root2))
194353
195012
  pruneStale(root2, live);
194354
195013
  return { written, proposed };
194355
195014
  }
194356
195015
  function walkAuthoredTcFiles(rootDir, visit) {
194357
- if (!fs41.existsSync(rootDir))
195016
+ if (!fs42.existsSync(rootDir))
194358
195017
  return;
194359
- for (const entry of fs41.readdirSync(rootDir, { withFileTypes: true })) {
195018
+ for (const entry of fs42.readdirSync(rootDir, { withFileTypes: true })) {
194360
195019
  if (entry.isDirectory()) {
194361
195020
  if (entry.name === INFERRED_DIR)
194362
195021
  continue;
@@ -194372,22 +195031,22 @@ function toRelCode(codeDir, fp) {
194372
195031
  }
194373
195032
  function pruneStale(root2, live) {
194374
195033
  const visit = (dir) => {
194375
- if (!fs41.existsSync(dir))
195034
+ if (!fs42.existsSync(dir))
194376
195035
  return false;
194377
195036
  let dirEmpty = true;
194378
- for (const entry of fs41.readdirSync(dir, { withFileTypes: true })) {
195037
+ for (const entry of fs42.readdirSync(dir, { withFileTypes: true })) {
194379
195038
  const full = path48.join(dir, entry.name);
194380
195039
  if (entry.isDirectory()) {
194381
195040
  if (visit(full)) {
194382
195041
  try {
194383
- fs41.rmdirSync(full);
195042
+ fs42.rmdirSync(full);
194384
195043
  } catch {
194385
195044
  }
194386
195045
  } else {
194387
195046
  dirEmpty = false;
194388
195047
  }
194389
195048
  } else if (entry.isFile() && entry.name.endsWith(".tc") && !live.has(full)) {
194390
- fs41.unlinkSync(full);
195049
+ fs42.unlinkSync(full);
194391
195050
  } else {
194392
195051
  dirEmpty = false;
194393
195052
  }
@@ -194553,6 +195212,7 @@ async function runAdd(options = {}) {
194553
195212
  // tools/cli/src/commands/analyze.ts
194554
195213
  init_dist4();
194555
195214
  init_transport();
195215
+ import fs17 from "node:fs";
194556
195216
  import path21 from "node:path";
194557
195217
 
194558
195218
  // packages/core/dist/commands/analyze-in-process.js
@@ -194842,6 +195502,30 @@ async function resolveOrInitProject() {
194842
195502
  ensureRepoTruecourseDir(repoDir);
194843
195503
  return registerProject(repoDir);
194844
195504
  }
195505
+ function repoHasCSharp(root2) {
195506
+ const SKIP = /* @__PURE__ */ new Set(["node_modules", "bin", "obj", ".git", ".truecourse", "dist", ".vs"]);
195507
+ let budget = 2e4;
195508
+ const stack2 = [root2];
195509
+ while (stack2.length > 0) {
195510
+ if (budget-- <= 0) return false;
195511
+ const dir = stack2.pop();
195512
+ let entries;
195513
+ try {
195514
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
195515
+ } catch {
195516
+ continue;
195517
+ }
195518
+ for (const e of entries) {
195519
+ if (e.isDirectory()) {
195520
+ if (SKIP.has(e.name) || e.name.startsWith(".")) continue;
195521
+ stack2.push(path21.join(dir, e.name));
195522
+ } else if (e.name.endsWith(".cs")) {
195523
+ return true;
195524
+ }
195525
+ }
195526
+ }
195527
+ return false;
195528
+ }
194845
195529
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
194846
195530
  var PRE_LLM_STEPS = /* @__PURE__ */ new Set(["parse", "scan"]);
194847
195531
  var spinnerFrame = 0;
@@ -194980,7 +195664,7 @@ async function runAnalyze(options = {}) {
194980
195664
  if (wipeLegacyPostgresData()) {
194981
195665
  O2.info("Legacy Postgres data wiped. Re-analyze to repopulate.");
194982
195666
  }
194983
- const stepDefs = buildAnalysisSteps(enabledCategories, enableLlmRules);
195667
+ const stepDefs = buildAnalysisSteps(enabledCategories, enableLlmRules, repoHasCSharp(project.path));
194984
195668
  const tracker = new StepTracker((payload) => {
194985
195669
  if (payload.steps) renderSteps(payload.steps);
194986
195670
  }, stepDefs);
@@ -195054,7 +195738,7 @@ async function runAnalyzeDiff(options = {}) {
195054
195738
  const enableLlmRules = llmDecision.enabled;
195055
195739
  if (enableLlmRules) await preflightClaudeOrExit();
195056
195740
  renderPhase = enableLlmRules ? "pre-llm" : "all";
195057
- const stepDefs = buildAnalysisSteps(enabledCategories, enableLlmRules);
195741
+ const stepDefs = buildAnalysisSteps(enabledCategories, enableLlmRules, repoHasCSharp(project.path));
195058
195742
  const tracker = new StepTracker((payload) => {
195059
195743
  if (payload.steps) renderSteps(payload.steps);
195060
195744
  }, stepDefs);
@@ -195117,24 +195801,24 @@ init_paths();
195117
195801
  init_registry();
195118
195802
  init_helpers();
195119
195803
  import { spawn as spawn7 } from "node:child_process";
195120
- import fs21 from "node:fs";
195804
+ import fs22 from "node:fs";
195121
195805
  import path26 from "node:path";
195122
195806
  import { fileURLToPath as fileURLToPath7 } from "node:url";
195123
195807
 
195124
195808
  // tools/cli/src/commands/service/macos.ts
195125
- import fs18 from "node:fs";
195809
+ import fs19 from "node:fs";
195126
195810
  import path22 from "node:path";
195127
195811
  import os5 from "node:os";
195128
195812
  import { execSync } from "node:child_process";
195129
195813
 
195130
195814
  // tools/cli/src/commands/service/env.ts
195131
- import fs17 from "node:fs";
195815
+ import fs18 from "node:fs";
195132
195816
  function parseEnvFile(filePath) {
195133
195817
  const vars = {};
195134
- if (!fs17.existsSync(filePath)) {
195818
+ if (!fs18.existsSync(filePath)) {
195135
195819
  return vars;
195136
195820
  }
195137
- const content = fs17.readFileSync(filePath, "utf-8");
195821
+ const content = fs18.readFileSync(filePath, "utf-8");
195138
195822
  for (const line of content.split("\n")) {
195139
195823
  const trimmed2 = line.trim();
195140
195824
  if (!trimmed2 || trimmed2.startsWith("#")) continue;
@@ -195205,10 +195889,10 @@ var MacOSService = class {
195205
195889
  }
195206
195890
  envVars.TRUECOURSE_LOG_DIR = path22.dirname(logPath);
195207
195891
  envVars.TRUECOURSE_HOME = path22.join(os5.homedir(), ".truecourse");
195208
- fs18.mkdirSync(PLIST_DIR, { recursive: true });
195209
- fs18.mkdirSync(path22.dirname(logPath), { recursive: true });
195892
+ fs19.mkdirSync(PLIST_DIR, { recursive: true });
195893
+ fs19.mkdirSync(path22.dirname(logPath), { recursive: true });
195210
195894
  const plist = buildPlist(serverPath, logPath, envVars);
195211
- fs18.writeFileSync(PLIST_PATH, plist, "utf-8");
195895
+ fs19.writeFileSync(PLIST_PATH, plist, "utf-8");
195212
195896
  execSync(`launchctl load -w "${PLIST_PATH}"`, { stdio: "pipe" });
195213
195897
  }
195214
195898
  async uninstall() {
@@ -195216,12 +195900,12 @@ var MacOSService = class {
195216
195900
  execSync(`launchctl unload "${PLIST_PATH}"`, { stdio: "pipe" });
195217
195901
  } catch {
195218
195902
  }
195219
- if (fs18.existsSync(PLIST_PATH)) {
195220
- fs18.unlinkSync(PLIST_PATH);
195903
+ if (fs19.existsSync(PLIST_PATH)) {
195904
+ fs19.unlinkSync(PLIST_PATH);
195221
195905
  }
195222
195906
  }
195223
195907
  async start() {
195224
- if (!fs18.existsSync(PLIST_PATH)) {
195908
+ if (!fs19.existsSync(PLIST_PATH)) {
195225
195909
  throw new Error("Service is not installed. Run 'truecourse service install' first.");
195226
195910
  }
195227
195911
  execSync(`launchctl load -w "${PLIST_PATH}"`, { stdio: "pipe" });
@@ -195251,12 +195935,12 @@ var MacOSService = class {
195251
195935
  }
195252
195936
  }
195253
195937
  async isInstalled() {
195254
- return fs18.existsSync(PLIST_PATH);
195938
+ return fs19.existsSync(PLIST_PATH);
195255
195939
  }
195256
195940
  };
195257
195941
 
195258
195942
  // tools/cli/src/commands/service/linux.ts
195259
- import fs19 from "node:fs";
195943
+ import fs20 from "node:fs";
195260
195944
  import path23 from "node:path";
195261
195945
  import os6 from "node:os";
195262
195946
  import { execSync as execSync2 } from "node:child_process";
@@ -195288,10 +195972,10 @@ WantedBy=default.target
195288
195972
  }
195289
195973
  var LinuxService = class {
195290
195974
  async install(serverPath, logPath) {
195291
- fs19.mkdirSync(UNIT_DIR, { recursive: true });
195292
- fs19.mkdirSync(path23.dirname(logPath), { recursive: true });
195975
+ fs20.mkdirSync(UNIT_DIR, { recursive: true });
195976
+ fs20.mkdirSync(path23.dirname(logPath), { recursive: true });
195293
195977
  const unit = buildUnitFile(serverPath, logPath);
195294
- fs19.writeFileSync(UNIT_PATH, unit, "utf-8");
195978
+ fs20.writeFileSync(UNIT_PATH, unit, "utf-8");
195295
195979
  execSync2("systemctl --user daemon-reload", { stdio: "pipe" });
195296
195980
  execSync2(`systemctl --user enable ${SERVICE_NAME}`, { stdio: "pipe" });
195297
195981
  }
@@ -195304,8 +195988,8 @@ var LinuxService = class {
195304
195988
  execSync2(`systemctl --user disable ${SERVICE_NAME}`, { stdio: "pipe" });
195305
195989
  } catch {
195306
195990
  }
195307
- if (fs19.existsSync(UNIT_PATH)) {
195308
- fs19.unlinkSync(UNIT_PATH);
195991
+ if (fs20.existsSync(UNIT_PATH)) {
195992
+ fs20.unlinkSync(UNIT_PATH);
195309
195993
  }
195310
195994
  try {
195311
195995
  execSync2("systemctl --user daemon-reload", { stdio: "pipe" });
@@ -195334,7 +196018,7 @@ var LinuxService = class {
195334
196018
  }
195335
196019
  }
195336
196020
  async isInstalled() {
195337
- return fs19.existsSync(UNIT_PATH);
196021
+ return fs20.existsSync(UNIT_PATH);
195338
196022
  }
195339
196023
  };
195340
196024
 
@@ -195444,7 +196128,7 @@ function getPlatform() {
195444
196128
  }
195445
196129
 
195446
196130
  // tools/cli/src/commands/service/logs.ts
195447
- import fs20 from "node:fs";
196131
+ import fs21 from "node:fs";
195448
196132
  import path25 from "node:path";
195449
196133
  import os8 from "node:os";
195450
196134
  var MAX_LOG_SIZE2 = 10 * 1024 * 1024;
@@ -195462,32 +196146,32 @@ function getLogPath() {
195462
196146
  return path25.join(getLogDir(), "dashboard.log");
195463
196147
  }
195464
196148
  function existingLogFiles(logDir) {
195465
- if (!fs20.existsSync(logDir)) return [];
195466
- return fs20.readdirSync(logDir).filter((name) => /\.log$/.test(name)).sort().map((name) => path25.join(logDir, name));
196149
+ if (!fs21.existsSync(logDir)) return [];
196150
+ return fs21.readdirSync(logDir).filter((name) => /\.log$/.test(name)).sort().map((name) => path25.join(logDir, name));
195467
196151
  }
195468
196152
  function rotateOne(logFile) {
195469
- if (!fs20.existsSync(logFile)) return;
195470
- if (fs20.statSync(logFile).size < MAX_LOG_SIZE2) return;
196153
+ if (!fs21.existsSync(logFile)) return;
196154
+ if (fs21.statSync(logFile).size < MAX_LOG_SIZE2) return;
195471
196155
  for (let i = MAX_LOG_FILES2; i >= 1; i--) {
195472
196156
  const older = `${logFile}.${i}`;
195473
196157
  if (i === MAX_LOG_FILES2) {
195474
- if (fs20.existsSync(older)) fs20.unlinkSync(older);
196158
+ if (fs21.existsSync(older)) fs21.unlinkSync(older);
195475
196159
  } else {
195476
196160
  const newer = `${logFile}.${i + 1}`;
195477
- if (fs20.existsSync(older)) fs20.renameSync(older, newer);
196161
+ if (fs21.existsSync(older)) fs21.renameSync(older, newer);
195478
196162
  }
195479
196163
  }
195480
- fs20.renameSync(logFile, `${logFile}.1`);
196164
+ fs21.renameSync(logFile, `${logFile}.1`);
195481
196165
  }
195482
196166
  function rotateLogs(logDir) {
195483
196167
  for (const name of ROTATABLE_LOG_NAMES) {
195484
196168
  rotateOne(path25.join(logDir, name));
195485
196169
  }
195486
- if (!fs20.existsSync(logDir)) return;
196170
+ if (!fs21.existsSync(logDir)) return;
195487
196171
  for (const file of existingLogFiles(logDir)) rotateOne(file);
195488
196172
  }
195489
196173
  function readLastLines(filePath, maxLines) {
195490
- const content = fs20.readFileSync(filePath, "utf-8");
196174
+ const content = fs21.readFileSync(filePath, "utf-8");
195491
196175
  const lines = content.split("\n");
195492
196176
  if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
195493
196177
  return lines.slice(-maxLines).join("\n");
@@ -195515,17 +196199,17 @@ function tailLogs() {
195515
196199
  }
195516
196200
  dumpLogTails(logDir);
195517
196201
  const sizes = /* @__PURE__ */ new Map();
195518
- for (const file of files) sizes.set(file, fs20.statSync(file).size);
196202
+ for (const file of files) sizes.set(file, fs21.statSync(file).size);
195519
196203
  const watch = (file) => {
195520
- fs20.watchFile(file, { interval: POLL_INTERVAL_MS }, () => {
196204
+ fs21.watchFile(file, { interval: POLL_INTERVAL_MS }, () => {
195521
196205
  try {
195522
- const newSize = fs20.statSync(file).size;
196206
+ const newSize = fs21.statSync(file).size;
195523
196207
  const lastSize = sizes.get(file) ?? 0;
195524
196208
  if (newSize > lastSize) {
195525
- const fd = fs20.openSync(file, "r");
196209
+ const fd = fs21.openSync(file, "r");
195526
196210
  const buf = Buffer.alloc(newSize - lastSize);
195527
- fs20.readSync(fd, buf, 0, buf.length, lastSize);
195528
- fs20.closeSync(fd);
196211
+ fs21.readSync(fd, buf, 0, buf.length, lastSize);
196212
+ fs21.closeSync(fd);
195529
196213
  const tag = `[${path25.basename(file)}] `;
195530
196214
  const text4 = buf.toString("utf-8");
195531
196215
  const tagged = text4.split("\n").map((line, i, arr) => i === arr.length - 1 && line === "" ? "" : tag + line).join("\n");
@@ -195540,7 +196224,7 @@ function tailLogs() {
195540
196224
  };
195541
196225
  for (const file of files) watch(file);
195542
196226
  const cleanup = () => {
195543
- for (const file of files) fs20.unwatchFile(file);
196227
+ for (const file of files) fs21.unwatchFile(file);
195544
196228
  process.exit(0);
195545
196229
  };
195546
196230
  process.on("SIGINT", cleanup);
@@ -195556,7 +196240,7 @@ function resolveServerEntry() {
195556
196240
  // Source build output: tools/cli/dist → ../../../../dist/server.mjs
195557
196241
  path26.resolve(__dirname2, "..", "..", "..", "..", "dist", "server.mjs")
195558
196242
  ];
195559
- return candidates.find((p2) => fs21.existsSync(p2)) ?? null;
196243
+ return candidates.find((p2) => fs22.existsSync(p2)) ?? null;
195560
196244
  }
195561
196245
  async function waitForHealth(url, timeoutMs = 3e4) {
195562
196246
  const start = Date.now();
@@ -195684,7 +196368,7 @@ async function runDashboard(options = {}) {
195684
196368
  );
195685
196369
  process.exit(1);
195686
196370
  }
195687
- const configured = fs21.existsSync(getConfigPath());
196371
+ const configured = fs22.existsSync(getConfigPath());
195688
196372
  const needsDecision = !configured || options.reconfigure;
195689
196373
  let runMode;
195690
196374
  if (options.mode) {
@@ -196180,7 +196864,7 @@ async function runRulesReset({ ruleKey }) {
196180
196864
 
196181
196865
  // tools/cli/src/commands/contracts.ts
196182
196866
  init_dist4();
196183
- import fs67 from "node:fs";
196867
+ import fs68 from "node:fs";
196184
196868
  import path73 from "node:path";
196185
196869
 
196186
196870
  // packages/spec-consolidator/dist/types.js
@@ -196414,7 +197098,7 @@ var CuratedCorpusSchema = external_exports.object({
196414
197098
  });
196415
197099
 
196416
197100
  // packages/spec-consolidator/dist/corpus-store.js
196417
- import fs22 from "node:fs";
197101
+ import fs23 from "node:fs";
196418
197102
  import path27 from "node:path";
196419
197103
  var CORPUS_FILE = "corpus.json";
196420
197104
  function corpusFilePath(repoRoot5) {
@@ -196422,10 +197106,10 @@ function corpusFilePath(repoRoot5) {
196422
197106
  }
196423
197107
  function readCorpus(repoRoot5) {
196424
197108
  const file = corpusFilePath(repoRoot5);
196425
- if (!fs22.existsSync(file))
197109
+ if (!fs23.existsSync(file))
196426
197110
  return null;
196427
197111
  try {
196428
- const raw = JSON.parse(fs22.readFileSync(file, "utf-8"));
197112
+ const raw = JSON.parse(fs23.readFileSync(file, "utf-8"));
196429
197113
  return CuratedCorpusSchema.parse(raw);
196430
197114
  } catch {
196431
197115
  return null;
@@ -196433,7 +197117,7 @@ function readCorpus(repoRoot5) {
196433
197117
  }
196434
197118
  function writeCorpus(repoRoot5, input) {
196435
197119
  const file = corpusFilePath(repoRoot5);
196436
- fs22.mkdirSync(path27.dirname(file), { recursive: true });
197120
+ fs23.mkdirSync(path27.dirname(file), { recursive: true });
196437
197121
  const payload = {
196438
197122
  version: 3,
196439
197123
  // Caller may pass the timestamp it stamped on the in-memory corpus so the
@@ -196444,16 +197128,16 @@ function writeCorpus(repoRoot5, input) {
196444
197128
  relations: input.relations,
196445
197129
  skippedDocs: input.skippedDocs ?? []
196446
197130
  };
196447
- fs22.writeFileSync(file, JSON.stringify(payload, null, 2) + "\n");
197131
+ fs23.writeFileSync(file, JSON.stringify(payload, null, 2) + "\n");
196448
197132
  }
196449
197133
 
196450
197134
  // packages/spec-consolidator/dist/area-tagger.js
196451
197135
  init_zod();
196452
197136
  import { createHash as createHash3 } from "node:crypto";
196453
- import fs24 from "node:fs";
197137
+ import fs25 from "node:fs";
196454
197138
 
196455
197139
  // packages/llm/dist/cache-store.js
196456
- import fs23 from "node:fs";
197140
+ import fs24 from "node:fs";
196457
197141
  import path28 from "node:path";
196458
197142
  var FileKvCacheStore = class {
196459
197143
  file(scope, cacheName, key4) {
@@ -196461,18 +197145,18 @@ var FileKvCacheStore = class {
196461
197145
  }
196462
197146
  async get(scope, cacheName, key4) {
196463
197147
  const file = this.file(scope, cacheName, key4);
196464
- if (!fs23.existsSync(file))
197148
+ if (!fs24.existsSync(file))
196465
197149
  return null;
196466
197150
  try {
196467
- return JSON.parse(fs23.readFileSync(file, "utf-8"));
197151
+ return JSON.parse(fs24.readFileSync(file, "utf-8"));
196468
197152
  } catch {
196469
197153
  return null;
196470
197154
  }
196471
197155
  }
196472
197156
  async set(scope, cacheName, key4, value) {
196473
197157
  const file = this.file(scope, cacheName, key4);
196474
- fs23.mkdirSync(path28.dirname(file), { recursive: true });
196475
- fs23.writeFileSync(file, JSON.stringify(value, null, 2));
197158
+ fs24.mkdirSync(path28.dirname(file), { recursive: true });
197159
+ fs24.writeFileSync(file, JSON.stringify(value, null, 2));
196476
197160
  }
196477
197161
  };
196478
197162
  var active6 = new FileKvCacheStore();
@@ -196554,7 +197238,7 @@ function docBody(doc) {
196554
197238
  return doc.content;
196555
197239
  if (doc.absPath) {
196556
197240
  try {
196557
- return fs24.readFileSync(doc.absPath, "utf-8");
197241
+ return fs25.readFileSync(doc.absPath, "utf-8");
196558
197242
  } catch {
196559
197243
  }
196560
197244
  }
@@ -197159,7 +197843,7 @@ function spawnRelationRunner(opts = {}) {
197159
197843
  // packages/spec-consolidator/dist/overlap-detector.js
197160
197844
  init_zod();
197161
197845
  import { createHash as createHash7 } from "node:crypto";
197162
- import fs25 from "node:fs";
197846
+ import fs26 from "node:fs";
197163
197847
  init_transport();
197164
197848
  var DEFAULT_MAX_PAIRS_PER_AREA = 60;
197165
197849
  async function flagOverlaps(repoRoot5, areas, docs, opts = {}) {
@@ -197340,7 +198024,7 @@ function docBody2(doc) {
197340
198024
  return doc.content;
197341
198025
  if (doc.absPath) {
197342
198026
  try {
197343
- return fs25.readFileSync(doc.absPath, "utf-8");
198027
+ return fs26.readFileSync(doc.absPath, "utf-8");
197344
198028
  } catch {
197345
198029
  }
197346
198030
  }
@@ -197432,14 +198116,14 @@ async function writeCache3(scope, cacheKey, verdict) {
197432
198116
 
197433
198117
  // packages/spec-consolidator/dist/curate.js
197434
198118
  init_dist5();
197435
- import fs28 from "node:fs";
198119
+ import fs29 from "node:fs";
197436
198120
  import path31 from "node:path";
197437
198121
 
197438
198122
  // packages/spec-consolidator/dist/discovery.js
197439
198123
  init_dist5();
197440
198124
  import { execFileSync as execFileSync2 } from "node:child_process";
197441
198125
  import { createHash as createHash8 } from "node:crypto";
197442
- import fs26 from "node:fs";
198126
+ import fs27 from "node:fs";
197443
198127
  import path30 from "node:path";
197444
198128
  var SKIP_DIR_PROBE = "__tc_skipdir_probe__.md";
197445
198129
  var PREVIEW_LINE_LIMIT = 200;
@@ -197451,7 +198135,7 @@ function discoverDocs(rootDir, opts = {}) {
197451
198135
  const visit = (dir) => {
197452
198136
  let entries;
197453
198137
  try {
197454
- entries = fs26.readdirSync(dir, { withFileTypes: true });
198138
+ entries = fs27.readdirSync(dir, { withFileTypes: true });
197455
198139
  } catch {
197456
198140
  return;
197457
198141
  }
@@ -197490,8 +198174,8 @@ function makeCandidate(absPath, rootDir, previewLines, opts) {
197490
198174
  let content;
197491
198175
  let stat;
197492
198176
  try {
197493
- content = fs26.readFileSync(absPath, "utf-8");
197494
- stat = fs26.statSync(absPath);
198177
+ content = fs27.readFileSync(absPath, "utf-8");
198178
+ stat = fs27.statSync(absPath);
197495
198179
  } catch {
197496
198180
  return null;
197497
198181
  }
@@ -197559,7 +198243,7 @@ function looksLikePrd(content) {
197559
198243
  // packages/spec-consolidator/dist/relevance-filter.js
197560
198244
  init_zod();
197561
198245
  import { createHash as createHash9 } from "node:crypto";
197562
- import fs27 from "node:fs";
198246
+ import fs28 from "node:fs";
197563
198247
  init_transport();
197564
198248
  function prefilterDocs(docs, manualIncludes = []) {
197565
198249
  const manualSet = new Set(manualIncludes);
@@ -197683,7 +198367,7 @@ function docBody3(doc) {
197683
198367
  return doc.content;
197684
198368
  if (doc.absPath) {
197685
198369
  try {
197686
- return fs27.readFileSync(doc.absPath, "utf-8");
198370
+ return fs28.readFileSync(doc.absPath, "utf-8");
197687
198371
  } catch {
197688
198372
  }
197689
198373
  }
@@ -197944,10 +198628,10 @@ var EMPTY_DECISIONS = {
197944
198628
  };
197945
198629
  function readCorpusDecisions(repoRoot5) {
197946
198630
  const file = path31.join(repoRoot5, ".truecourse", "specs", "decisions.json");
197947
- if (!fs28.existsSync(file))
198631
+ if (!fs29.existsSync(file))
197948
198632
  return EMPTY_DECISIONS;
197949
198633
  try {
197950
- const raw = JSON.parse(fs28.readFileSync(file, "utf-8"));
198634
+ const raw = JSON.parse(fs29.readFileSync(file, "utf-8"));
197951
198635
  return DecisionsFileSchema.parse(raw);
197952
198636
  } catch {
197953
198637
  return EMPTY_DECISIONS;
@@ -197955,7 +198639,7 @@ function readCorpusDecisions(repoRoot5) {
197955
198639
  }
197956
198640
 
197957
198641
  // packages/spec-consolidator/dist/orchestrator.js
197958
- import fs29 from "node:fs";
198642
+ import fs30 from "node:fs";
197959
198643
  import path32 from "node:path";
197960
198644
  var EMPTY_DECISIONS2 = {
197961
198645
  version: 1,
@@ -197969,10 +198653,10 @@ function decisionsPath(repoRoot5) {
197969
198653
  }
197970
198654
  function readDecisions(repoRoot5) {
197971
198655
  const file = decisionsPath(repoRoot5);
197972
- if (!fs29.existsSync(file))
198656
+ if (!fs30.existsSync(file))
197973
198657
  return EMPTY_DECISIONS2;
197974
198658
  try {
197975
- const raw = JSON.parse(fs29.readFileSync(file, "utf-8"));
198659
+ const raw = JSON.parse(fs30.readFileSync(file, "utf-8"));
197976
198660
  return DecisionsFileSchema.parse(raw);
197977
198661
  } catch {
197978
198662
  return EMPTY_DECISIONS2;
@@ -197980,8 +198664,8 @@ function readDecisions(repoRoot5) {
197980
198664
  }
197981
198665
  function writeDecisions(repoRoot5, decisions) {
197982
198666
  const file = decisionsPath(repoRoot5);
197983
- fs29.mkdirSync(path32.dirname(file), { recursive: true });
197984
- fs29.writeFileSync(file, JSON.stringify(decisions, null, 2) + "\n");
198667
+ fs30.mkdirSync(path32.dirname(file), { recursive: true });
198668
+ fs30.writeFileSync(file, JSON.stringify(decisions, null, 2) + "\n");
197985
198669
  }
197986
198670
 
197987
198671
  // packages/contract-extractor/dist/corpus-diff.js
@@ -198133,7 +198817,7 @@ function validateMerged(artifacts) {
198133
198817
  }
198134
198818
 
198135
198819
  // packages/contract-extractor/dist/writer.js
198136
- import fs42 from "node:fs";
198820
+ import fs43 from "node:fs";
198137
198821
  import path49 from "node:path";
198138
198822
 
198139
198823
  // packages/contract-extractor/dist/identity.js
@@ -198170,14 +198854,14 @@ function writeContracts(repoRoot5, artifacts, options = {}) {
198170
198854
  proposed.push(req.filePath);
198171
198855
  continue;
198172
198856
  }
198173
- fs42.mkdirSync(path49.dirname(req.filePath), { recursive: true });
198174
- const existing = fs42.existsSync(req.filePath) ? fs42.readFileSync(req.filePath, "utf-8") : null;
198857
+ fs43.mkdirSync(path49.dirname(req.filePath), { recursive: true });
198858
+ const existing = fs43.existsSync(req.filePath) ? fs43.readFileSync(req.filePath, "utf-8") : null;
198175
198859
  if (existing === req.tcSource)
198176
198860
  continue;
198177
- fs42.writeFileSync(req.filePath, req.tcSource);
198861
+ fs43.writeFileSync(req.filePath, req.tcSource);
198178
198862
  written.push(req.filePath);
198179
198863
  }
198180
- if (options.prune && !options.dryRun && fs42.existsSync(root2)) {
198864
+ if (options.prune && !options.dryRun && fs43.existsSync(root2)) {
198181
198865
  const live = new Set(requests.map((r) => r.filePath));
198182
198866
  pruneStale2(root2, live);
198183
198867
  }
@@ -198243,16 +198927,16 @@ function inferKindDomain2(artifact) {
198243
198927
  }
198244
198928
  function pruneStale2(root2, live) {
198245
198929
  const visit = (dir) => {
198246
- if (!fs42.existsSync(dir))
198930
+ if (!fs43.existsSync(dir))
198247
198931
  return false;
198248
198932
  let dirEmpty = true;
198249
- for (const entry of fs42.readdirSync(dir, { withFileTypes: true })) {
198933
+ for (const entry of fs43.readdirSync(dir, { withFileTypes: true })) {
198250
198934
  const full = path49.join(dir, entry.name);
198251
198935
  if (entry.isDirectory()) {
198252
198936
  const childEmpty = visit(full);
198253
198937
  if (childEmpty) {
198254
198938
  try {
198255
- fs42.rmdirSync(full);
198939
+ fs43.rmdirSync(full);
198256
198940
  } catch {
198257
198941
  }
198258
198942
  } else {
@@ -198260,7 +198944,7 @@ function pruneStale2(root2, live) {
198260
198944
  }
198261
198945
  } else if (entry.isFile()) {
198262
198946
  if (entry.name.endsWith(".tc") && !live.has(full)) {
198263
- fs42.unlinkSync(full);
198947
+ fs43.unlinkSync(full);
198264
198948
  } else {
198265
198949
  dirEmpty = false;
198266
198950
  }
@@ -201192,7 +201876,7 @@ async function assembleArtifacts(ranked, slices, opts = {}) {
201192
201876
  }
201193
201877
 
201194
201878
  // packages/contract-extractor/dist/corpus-reader.js
201195
- import fs43 from "node:fs";
201879
+ import fs44 from "node:fs";
201196
201880
  import path50 from "node:path";
201197
201881
  function hasCorpusSpec(repoRoot5) {
201198
201882
  return readCorpus(repoRoot5) !== null;
@@ -201290,7 +201974,7 @@ function orderByPrecedence(docs, areaId, relations) {
201290
201974
  }
201291
201975
  function defaultResolveContent(repoRoot5, ref) {
201292
201976
  try {
201293
- return fs43.readFileSync(path50.join(repoRoot5, ref), "utf-8");
201977
+ return fs44.readFileSync(path50.join(repoRoot5, ref), "utf-8");
201294
201978
  } catch {
201295
201979
  return null;
201296
201980
  }
@@ -201431,7 +202115,7 @@ init_transport();
201431
202115
  init_dist8();
201432
202116
 
201433
202117
  // packages/contract-extractor/dist/manifest.js
201434
- import fs44 from "node:fs";
202118
+ import fs45 from "node:fs";
201435
202119
  import path51 from "node:path";
201436
202120
  import { createHash as createHash12 } from "node:crypto";
201437
202121
  var MANIFEST_REL = path51.join(".truecourse", "contracts", "manifest.json");
@@ -201450,7 +202134,7 @@ function manifestPath(repoRoot5) {
201450
202134
  }
201451
202135
  function readManifest(repoRoot5) {
201452
202136
  try {
201453
- const raw = JSON.parse(fs44.readFileSync(manifestPath(repoRoot5), "utf-8"));
202137
+ const raw = JSON.parse(fs45.readFileSync(manifestPath(repoRoot5), "utf-8"));
201454
202138
  if (raw && raw.version === 1 && raw.areas)
201455
202139
  return raw;
201456
202140
  } catch {
@@ -201459,8 +202143,8 @@ function readManifest(repoRoot5) {
201459
202143
  }
201460
202144
  function writeManifest(repoRoot5, manifest) {
201461
202145
  const file = manifestPath(repoRoot5);
201462
- fs44.mkdirSync(path51.dirname(file), { recursive: true });
201463
- fs44.writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n");
202146
+ fs45.mkdirSync(path51.dirname(file), { recursive: true });
202147
+ fs45.writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n");
201464
202148
  }
201465
202149
  function classifyAreas(areas, manifest) {
201466
202150
  const prev = manifest?.areas ?? {};
@@ -201919,7 +202603,7 @@ function spawnGenerateRunner(opts = {}) {
201919
202603
 
201920
202604
  // packages/core/dist/config/llm-models.js
201921
202605
  init_paths();
201922
- import fs45 from "node:fs";
202606
+ import fs46 from "node:fs";
201923
202607
  var STAGE_DEFAULTS = {
201924
202608
  "spec.relevance": "haiku",
201925
202609
  // Area tagging is load-bearing (wrong tags → wrong generate inputs) and Haiku
@@ -201967,10 +202651,10 @@ function stageEnvVar(stageId) {
201967
202651
  }
201968
202652
  function readConfigSync(repoDir) {
201969
202653
  const file = getRepoConfigPath(repoDir);
201970
- if (!fs45.existsSync(file))
202654
+ if (!fs46.existsSync(file))
201971
202655
  return {};
201972
202656
  try {
201973
- return JSON.parse(fs45.readFileSync(file, "utf-8"));
202657
+ return JSON.parse(fs46.readFileSync(file, "utf-8"));
201974
202658
  } catch {
201975
202659
  return {};
201976
202660
  }
@@ -202043,7 +202727,7 @@ function describeStageResolutions(repoDir = resolveRepoDir(process.cwd())) {
202043
202727
  init_transport();
202044
202728
 
202045
202729
  // packages/core/dist/lib/llm-call-log.js
202046
- import fs46 from "node:fs";
202730
+ import fs47 from "node:fs";
202047
202731
  import path52 from "node:path";
202048
202732
  import { randomUUID as randomUUID26 } from "node:crypto";
202049
202733
  function truthyEnv(v) {
@@ -202065,20 +202749,20 @@ function createLlmCallLogger(repoRoot5, label = "scan") {
202065
202749
  if (!log2 && !dump2)
202066
202750
  return null;
202067
202751
  const logDir = path52.join(repoRoot5, ".truecourse", "logs");
202068
- fs46.mkdirSync(logDir, { recursive: true });
202752
+ fs47.mkdirSync(logDir, { recursive: true });
202069
202753
  const runId = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}_${randomUUID26().slice(0, 8)}`;
202070
202754
  const callsPath = path52.join(logDir, `llm-${label}-${runId}.jsonl`);
202071
202755
  const summaryPath = path52.join(logDir, `llm-${label}-${runId}.summary.json`);
202072
202756
  const ioDir = dump2 ? path52.join(logDir, `llm-${label}-${runId}.io`) : null;
202073
202757
  if (ioDir)
202074
- fs46.mkdirSync(ioDir, { recursive: true });
202758
+ fs47.mkdirSync(ioDir, { recursive: true });
202075
202759
  const createdAt = Date.now();
202076
202760
  const records = [];
202077
202761
  let seq2 = 0;
202078
202762
  let finished = false;
202079
202763
  let fd = null;
202080
202764
  try {
202081
- fd = fs46.openSync(callsPath, "a");
202765
+ fd = fs47.openSync(callsPath, "a");
202082
202766
  } catch {
202083
202767
  fd = null;
202084
202768
  }
@@ -202088,14 +202772,14 @@ function createLlmCallLogger(repoRoot5, label = "scan") {
202088
202772
  records.push(metrics);
202089
202773
  if (fd !== null) {
202090
202774
  try {
202091
- fs46.writeSync(fd, JSON.stringify(metrics) + "\n");
202775
+ fs47.writeSync(fd, JSON.stringify(metrics) + "\n");
202092
202776
  } catch {
202093
202777
  }
202094
202778
  }
202095
202779
  if (ioDir) {
202096
202780
  const fname = `${String(seq2).padStart(5, "0")}-${sanitize3(rec.id) || rec.stage}.json`;
202097
202781
  try {
202098
- fs46.writeFileSync(path52.join(ioDir, fname), JSON.stringify({
202782
+ fs47.writeFileSync(path52.join(ioDir, fname), JSON.stringify({
202099
202783
  stage: rec.stage,
202100
202784
  model: rec.model,
202101
202785
  ok: rec.ok,
@@ -202125,13 +202809,13 @@ function createLlmCallLogger(repoRoot5, label = "scan") {
202125
202809
  process.removeListener(sig, h);
202126
202810
  if (fd !== null) {
202127
202811
  try {
202128
- fs46.closeSync(fd);
202812
+ fs47.closeSync(fd);
202129
202813
  } catch {
202130
202814
  }
202131
202815
  }
202132
202816
  const summary = summarizeLlmCalls(records, elapsedMs);
202133
202817
  try {
202134
- fs46.writeFileSync(summaryPath, JSON.stringify(summary, null, 2));
202818
+ fs47.writeFileSync(summaryPath, JSON.stringify(summary, null, 2));
202135
202819
  } catch {
202136
202820
  }
202137
202821
  printSummary(summary, callsPath, ioDir);
@@ -202261,7 +202945,7 @@ import crypto5 from "node:crypto";
202261
202945
 
202262
202946
  // packages/guard-runner/dist/recipe.js
202263
202947
  init_zod();
202264
- import fs47 from "node:fs";
202948
+ import fs48 from "node:fs";
202265
202949
  import path53 from "node:path";
202266
202950
  import crypto3 from "node:crypto";
202267
202951
  var RecipeSchema = external_exports.object({
@@ -202281,11 +202965,11 @@ var FINGERPRINT_INPUTS = [
202281
202965
  var RecipeError = class extends Error {
202282
202966
  };
202283
202967
  function loadRecipe(repoRoot5, recipeFile) {
202284
- if (!fs47.existsSync(recipeFile))
202968
+ if (!fs48.existsSync(recipeFile))
202285
202969
  return null;
202286
202970
  let parsed;
202287
202971
  try {
202288
- parsed = JSON.parse(fs47.readFileSync(recipeFile, "utf-8"));
202972
+ parsed = JSON.parse(fs48.readFileSync(recipeFile, "utf-8"));
202289
202973
  } catch (e) {
202290
202974
  throw new RecipeError(`recipe.json is not valid JSON: ${e instanceof Error ? e.message : e}`);
202291
202975
  }
@@ -202299,11 +202983,11 @@ function computeRecipeFingerprint(repoRoot5) {
202299
202983
  const hash = crypto3.createHash("sha256");
202300
202984
  for (const rel of FINGERPRINT_INPUTS) {
202301
202985
  const abs = path53.join(repoRoot5, rel);
202302
- if (!fs47.existsSync(abs) || !fs47.statSync(abs).isFile())
202986
+ if (!fs48.existsSync(abs) || !fs48.statSync(abs).isFile())
202303
202987
  continue;
202304
202988
  hash.update(rel);
202305
202989
  hash.update("\0");
202306
- hash.update(fs47.readFileSync(abs));
202990
+ hash.update(fs48.readFileSync(abs));
202307
202991
  hash.update("\0");
202308
202992
  }
202309
202993
  return `sha256:${hash.digest("hex")}`;
@@ -202315,7 +202999,7 @@ function resolveEntry(repoRoot5, entry) {
202315
202999
  if (path53.isAbsolute(arg))
202316
203000
  return arg;
202317
203001
  const abs = path53.resolve(repoRoot5, arg);
202318
- return fs47.existsSync(abs) && fs47.statSync(abs).isFile() ? abs : arg;
203002
+ return fs48.existsSync(abs) && fs48.statSync(abs).isFile() ? abs : arg;
202319
203003
  });
202320
203004
  return [resolvedCommand, ...resolvedRest];
202321
203005
  }
@@ -202329,8 +203013,8 @@ function resolveOnHostPath(command) {
202329
203013
  for (const ext2 of exts) {
202330
203014
  const candidate = path53.join(dir, command + ext2);
202331
203015
  try {
202332
- if (fs47.statSync(candidate).isFile()) {
202333
- fs47.accessSync(candidate, fs47.constants.X_OK);
203016
+ if (fs48.statSync(candidate).isFile()) {
203017
+ fs48.accessSync(candidate, fs48.constants.X_OK);
202334
203018
  return candidate;
202335
203019
  }
202336
203020
  } catch {
@@ -202341,7 +203025,7 @@ function resolveOnHostPath(command) {
202341
203025
  }
202342
203026
 
202343
203027
  // packages/guard-runner/dist/scenario-loader.js
202344
- import fs49 from "node:fs";
203028
+ import fs50 from "node:fs";
202345
203029
  import path55 from "node:path";
202346
203030
 
202347
203031
  // node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/dist/js-yaml.mjs
@@ -204973,7 +205657,7 @@ init_dist5();
204973
205657
 
204974
205658
  // packages/guard-runner/dist/store.js
204975
205659
  init_dist5();
204976
- import fs48 from "node:fs";
205660
+ import fs49 from "node:fs";
204977
205661
  import path54 from "node:path";
204978
205662
  var TRUECOURSE_DIR3 = ".truecourse";
204979
205663
  var GUARD_DIR = "guard";
@@ -205029,10 +205713,10 @@ function sanitizeSegment(segment) {
205029
205713
  return segment.replace(/[^a-zA-Z0-9._-]/g, "_");
205030
205714
  }
205031
205715
  function atomicWriteJson2(targetPath, data) {
205032
- fs48.mkdirSync(path54.dirname(targetPath), { recursive: true });
205716
+ fs49.mkdirSync(path54.dirname(targetPath), { recursive: true });
205033
205717
  const tmp = `${targetPath}.tmp-${process.pid}-${Date.now()}`;
205034
- fs48.writeFileSync(tmp, JSON.stringify(data, null, 2));
205035
- fs48.renameSync(tmp, targetPath);
205718
+ fs49.writeFileSync(tmp, JSON.stringify(data, null, 2));
205719
+ fs49.renameSync(tmp, targetPath);
205036
205720
  }
205037
205721
  function writeGuardLatest(repoRoot5, latest) {
205038
205722
  const target = guardLatestPath(repoRoot5);
@@ -205064,10 +205748,10 @@ function readGuardResult(repoRoot5) {
205064
205748
  return readJsonOr(guardResultPath(repoRoot5), GuardGenerateReportSchema, null);
205065
205749
  }
205066
205750
  function readJsonOr(file, schema2, fallback) {
205067
- if (!fs48.existsSync(file))
205751
+ if (!fs49.existsSync(file))
205068
205752
  return fallback;
205069
205753
  try {
205070
- const parsed = schema2.safeParse(JSON.parse(fs48.readFileSync(file, "utf-8")));
205754
+ const parsed = schema2.safeParse(JSON.parse(fs49.readFileSync(file, "utf-8")));
205071
205755
  return parsed.success ? parsed.data : fallback;
205072
205756
  } catch {
205073
205757
  return fallback;
@@ -205076,10 +205760,10 @@ function readJsonOr(file, schema2, fallback) {
205076
205760
 
205077
205761
  // packages/guard-runner/dist/scenario-loader.js
205078
205762
  function collectScenarioFiles(dir) {
205079
- if (!fs49.existsSync(dir))
205763
+ if (!fs50.existsSync(dir))
205080
205764
  return [];
205081
205765
  const out = [];
205082
- for (const entry of fs49.readdirSync(dir, { withFileTypes: true })) {
205766
+ for (const entry of fs50.readdirSync(dir, { withFileTypes: true })) {
205083
205767
  const full = path55.join(dir, entry.name);
205084
205768
  if (entry.isDirectory())
205085
205769
  out.push(...collectScenarioFiles(full));
@@ -205096,7 +205780,7 @@ function loadScenarios(repoRoot5) {
205096
205780
  const rel = path55.relative(repoRoot5, file);
205097
205781
  let doc;
205098
205782
  try {
205099
- doc = jsYaml.load(fs49.readFileSync(file, "utf-8"));
205783
+ doc = jsYaml.load(fs50.readFileSync(file, "utf-8"));
205100
205784
  } catch (e) {
205101
205785
  errors.push({ file: rel, message: `YAML parse error: ${e instanceof Error ? e.message : e}` });
205102
205786
  continue;
@@ -205160,11 +205844,11 @@ ${err.message}`;
205160
205844
  }
205161
205845
 
205162
205846
  // packages/guard-runner/dist/preflight.js
205163
- import fs51 from "node:fs";
205847
+ import fs52 from "node:fs";
205164
205848
  import path57 from "node:path";
205165
205849
 
205166
205850
  // packages/guard-runner/dist/sandbox.js
205167
- import fs50 from "node:fs";
205851
+ import fs51 from "node:fs";
205168
205852
  import os11 from "node:os";
205169
205853
  import path56 from "node:path";
205170
205854
  var DETERMINISM_PINS = {
@@ -205176,13 +205860,13 @@ var DETERMINISM_PINS = {
205176
205860
  var SandboxError = class extends Error {
205177
205861
  };
205178
205862
  function createSandbox(opts = {}) {
205179
- const root2 = fs50.mkdtempSync(path56.join(os11.tmpdir(), "tc-guard-"));
205863
+ const root2 = fs51.mkdtempSync(path56.join(os11.tmpdir(), "tc-guard-"));
205180
205864
  const cwd = path56.join(root2, "work");
205181
205865
  const home = path56.join(root2, "home");
205182
205866
  const tmp = path56.join(root2, "tmp");
205183
- fs50.mkdirSync(cwd, { recursive: true });
205184
- fs50.mkdirSync(home, { recursive: true });
205185
- fs50.mkdirSync(tmp, { recursive: true });
205867
+ fs51.mkdirSync(cwd, { recursive: true });
205868
+ fs51.mkdirSync(home, { recursive: true });
205869
+ fs51.mkdirSync(tmp, { recursive: true });
205186
205870
  const xdg = {
205187
205871
  XDG_CONFIG_HOME: path56.join(home, ".config"),
205188
205872
  XDG_CACHE_HOME: path56.join(home, ".cache"),
@@ -205191,7 +205875,7 @@ function createSandbox(opts = {}) {
205191
205875
  XDG_RUNTIME_DIR: path56.join(home, "run")
205192
205876
  };
205193
205877
  for (const dir of Object.values(xdg))
205194
- fs50.mkdirSync(dir, { recursive: true });
205878
+ fs51.mkdirSync(dir, { recursive: true });
205195
205879
  const env = {
205196
205880
  // PATH: programs must resolve node/git; the only host value that passes through.
205197
205881
  PATH: process.env.PATH ?? "",
@@ -205214,7 +205898,7 @@ function createSandbox(opts = {}) {
205214
205898
  root: root2,
205215
205899
  env,
205216
205900
  cleanup() {
205217
- fs50.rmSync(root2, { recursive: true, force: true });
205901
+ fs51.rmSync(root2, { recursive: true, force: true });
205218
205902
  }
205219
205903
  };
205220
205904
  }
@@ -205224,14 +205908,14 @@ function seedFiles(cwd, files) {
205224
205908
  if (target !== cwd && !target.startsWith(cwd + path56.sep)) {
205225
205909
  throw new SandboxError(`setup.files path escapes the sandbox: ${rel}`);
205226
205910
  }
205227
- fs50.mkdirSync(path56.dirname(target), { recursive: true });
205228
- fs50.writeFileSync(target, content);
205911
+ fs51.mkdirSync(path56.dirname(target), { recursive: true });
205912
+ fs51.writeFileSync(target, content);
205229
205913
  }
205230
205914
  }
205231
205915
  function listSandboxFiles(dir) {
205232
205916
  const out = [];
205233
205917
  const walk20 = (current) => {
205234
- for (const entry of fs50.readdirSync(current, { withFileTypes: true })) {
205918
+ for (const entry of fs51.readdirSync(current, { withFileTypes: true })) {
205235
205919
  const full = path56.join(current, entry.name);
205236
205920
  if (entry.isDirectory())
205237
205921
  walk20(full);
@@ -205239,7 +205923,7 @@ function listSandboxFiles(dir) {
205239
205923
  out.push(path56.relative(dir, full));
205240
205924
  }
205241
205925
  };
205242
- if (fs50.existsSync(dir))
205926
+ if (fs51.existsSync(dir))
205243
205927
  walk20(dir);
205244
205928
  return out.sort();
205245
205929
  }
@@ -205382,10 +206066,10 @@ function missingEntryScript(repoRoot5, entry) {
205382
206066
  }
205383
206067
  for (const arg of candidates) {
205384
206068
  const resolved = path57.isAbsolute(arg) ? arg : path57.resolve(repoRoot5, arg);
205385
- if (fs51.existsSync(resolved))
206069
+ if (fs52.existsSync(resolved))
205386
206070
  continue;
205387
206071
  const parent = path57.dirname(resolved);
205388
- const siblings = fs51.existsSync(parent) ? fs51.readdirSync(parent).sort() : null;
206072
+ const siblings = fs52.existsSync(parent) ? fs52.readdirSync(parent).sort() : null;
205389
206073
  return {
205390
206074
  arg,
205391
206075
  resolved,
@@ -205420,7 +206104,7 @@ function formatEntryPreflightError(opts) {
205420
206104
 
205421
206105
  // packages/guard-runner/dist/capabilities/git.js
205422
206106
  import { spawnSync as spawnSync2 } from "node:child_process";
205423
- import fs52 from "node:fs";
206107
+ import fs53 from "node:fs";
205424
206108
  import path58 from "node:path";
205425
206109
  var GIT_IDENTITY = {
205426
206110
  GIT_AUTHOR_NAME: "TrueCourse Guard",
@@ -205457,7 +206141,7 @@ function requireFiles(files, ctx) {
205457
206141
  if (target !== ctx.cwd && !target.startsWith(ctx.cwd + path58.sep)) {
205458
206142
  throw new CapabilityError("git", `path escapes the sandbox: ${rel}`);
205459
206143
  }
205460
- if (!fs52.existsSync(target)) {
206144
+ if (!fs53.existsSync(target)) {
205461
206145
  throw new CapabilityError("git", `declared file does not exist in the sandbox: ${rel} (seed it via setup.files or an earlier commit)`);
205462
206146
  }
205463
206147
  }
@@ -205545,7 +206229,7 @@ function normalize7(text4, normalizers, ctx) {
205545
206229
  }
205546
206230
 
205547
206231
  // packages/guard-runner/dist/expect.js
205548
- import fs53 from "node:fs";
206232
+ import fs54 from "node:fs";
205549
206233
  import path59 from "node:path";
205550
206234
  function evaluateExpect(params) {
205551
206235
  const { expect } = params;
@@ -205618,7 +206302,7 @@ function matchStream(subject, matcher, value) {
205618
206302
  }
205619
206303
  function matchFile3(rel, matcher, sandboxCwd, normalizeText) {
205620
206304
  const target = path59.resolve(sandboxCwd, rel);
205621
- const exists2 = fs53.existsSync(target) && fs53.statSync(target).isFile();
206305
+ const exists2 = fs54.existsSync(target) && fs54.statSync(target).isFile();
205622
206306
  if (matcher.exists === true || matcher.absent === false) {
205623
206307
  if (!exists2)
205624
206308
  return fileMiss(rel, "to exist", "missing");
@@ -205630,7 +206314,7 @@ function matchFile3(rel, matcher, sandboxCwd, normalizeText) {
205630
206314
  if (matcher.equals !== void 0 || matcher.contains !== void 0) {
205631
206315
  if (!exists2)
205632
206316
  return fileMiss(rel, "to exist for a content check", "missing");
205633
- const content = normalizeText(fs53.readFileSync(target, "utf-8"));
206317
+ const content = normalizeText(fs54.readFileSync(target, "utf-8"));
205634
206318
  if (matcher.equals !== void 0 && content !== matcher.equals) {
205635
206319
  return {
205636
206320
  subject: "files",
@@ -205660,12 +206344,12 @@ function fileMiss(rel, expectedPhrase, actualState) {
205660
206344
  }
205661
206345
 
205662
206346
  // packages/guard-runner/dist/evidence.js
205663
- import fs54 from "node:fs";
206347
+ import fs55 from "node:fs";
205664
206348
  import path60 from "node:path";
205665
206349
  function writeEvidence(params) {
205666
206350
  const dir = evidenceScenarioDir(params.repoRoot, params.runId, params.scenarioId);
205667
- fs54.mkdirSync(dir, { recursive: true });
205668
- const failing = params.steps.find((s) => s.index === params.failingStep);
206351
+ fs55.mkdirSync(dir, { recursive: true });
206352
+ const focus = params.failingStep != null ? params.steps.find((s) => s.index === params.failingStep) : params.steps[params.steps.length - 1];
205669
206353
  const invocation = {
205670
206354
  scenarioId: params.scenarioId,
205671
206355
  title: params.title,
@@ -205685,11 +206369,11 @@ function writeEvidence(params) {
205685
206369
  }))
205686
206370
  };
205687
206371
  writeFile(dir, "invocation.json", JSON.stringify(invocation, null, 2));
205688
- if (failing) {
205689
- writeFile(dir, "stdout.raw.txt", failing.rawStdout);
205690
- writeFile(dir, "stdout.txt", failing.normStdout);
205691
- writeFile(dir, "stderr.raw.txt", failing.rawStderr);
205692
- writeFile(dir, "stderr.txt", failing.normStderr);
206372
+ if (focus) {
206373
+ writeFile(dir, "stdout.raw.txt", focus.rawStdout);
206374
+ writeFile(dir, "stdout.txt", focus.normStdout);
206375
+ writeFile(dir, "stderr.raw.txt", focus.rawStderr);
206376
+ writeFile(dir, "stderr.txt", focus.normStderr);
205693
206377
  }
205694
206378
  const diffLines = [];
205695
206379
  if (params.outcome === "fail" && params.mismatch) {
@@ -205697,8 +206381,10 @@ function writeEvidence(params) {
205697
206381
  diffLines.push(`expected: ${params.mismatch.expected}`);
205698
206382
  diffLines.push(`actual: ${params.mismatch.actual}`, "");
205699
206383
  diffLines.push(...params.mismatch.detail);
205700
- } else if (params.infraMessage) {
206384
+ } else if (params.outcome === "error" && params.infraMessage) {
205701
206385
  diffLines.push(`step ${params.failingStep} \u2014 infrastructure error`, "", params.infraMessage);
206386
+ } else if (params.outcome === "pass") {
206387
+ diffLines.push(`all ${params.steps.length} step${params.steps.length === 1 ? "" : "s"} met their expectations`);
205702
206388
  }
205703
206389
  writeFile(dir, "diff.txt", diffLines.join("\n") + "\n");
205704
206390
  writeFile(dir, "files.txt", listSandboxFiles(params.sandboxCwd).join("\n") + "\n");
@@ -205743,7 +206429,7 @@ function indent(text4) {
205743
206429
  return body.split("\n").map((l) => ` ${l}`).join("\n");
205744
206430
  }
205745
206431
  function writeFile(dir, name, content) {
205746
- fs54.writeFileSync(path60.join(dir, name), content);
206432
+ fs55.writeFileSync(path60.join(dir, name), content);
205747
206433
  }
205748
206434
 
205749
206435
  // packages/guard-runner/dist/run-scenario.js
@@ -205812,7 +206498,7 @@ async function runScenario(scenario, ctx) {
205812
206498
  if (capture.spawnError || capture.timedOut) {
205813
206499
  const infra = capture.timedOut ? `step timed out after ${ctx.stepTimeoutMs}ms` : `failed to spawn: ${capture.spawnError}`;
205814
206500
  records.push(toRecord(stepIndex, argv, step.stdin, repeat3, iteration, capture, normText));
205815
- const evidencePath = writeEvidence({
206501
+ const evidencePath2 = writeEvidence({
205816
206502
  repoRoot: ctx.repoRoot,
205817
206503
  runId: ctx.runId,
205818
206504
  scenarioId: scenario.id,
@@ -205830,7 +206516,7 @@ async function runScenario(scenario, ctx) {
205830
206516
  outcome: "error",
205831
206517
  durationMs: Date.now() - start,
205832
206518
  failure: { step: stepIndex, expected: "the step to run", actual: infra },
205833
- evidencePath
206519
+ evidencePath: evidencePath2
205834
206520
  };
205835
206521
  }
205836
206522
  const normStdout = normText(capture.stdout);
@@ -205845,7 +206531,7 @@ async function runScenario(scenario, ctx) {
205845
206531
  });
205846
206532
  if (mismatch) {
205847
206533
  records.push(toRecord(stepIndex, argv, step.stdin, repeat3, iteration, capture, normText));
205848
- const evidencePath = writeEvidence({
206534
+ const evidencePath2 = writeEvidence({
205849
206535
  repoRoot: ctx.repoRoot,
205850
206536
  runId: ctx.runId,
205851
206537
  scenarioId: scenario.id,
@@ -205867,7 +206553,7 @@ async function runScenario(scenario, ctx) {
205867
206553
  expected: mismatch.expected,
205868
206554
  actual: mismatch.actual
205869
206555
  },
205870
- evidencePath
206556
+ evidencePath: evidencePath2
205871
206557
  };
205872
206558
  }
205873
206559
  }
@@ -205875,7 +206561,18 @@ async function runScenario(scenario, ctx) {
205875
206561
  records.push(toRecord(stepIndex, argv, step.stdin, repeat3, repeat3, lastCapture, normText));
205876
206562
  }
205877
206563
  }
205878
- return { ...base2, outcome: "pass", durationMs: Date.now() - start };
206564
+ const evidencePath = ctx.capturePassEvidence ? writeEvidence({
206565
+ repoRoot: ctx.repoRoot,
206566
+ runId: ctx.runId,
206567
+ scenarioId: scenario.id,
206568
+ title: scenario.title,
206569
+ binds: scenario.binds,
206570
+ outcome: "pass",
206571
+ steps: records,
206572
+ sandboxCwd: sandbox.cwd,
206573
+ envPins: ENV_PINS
206574
+ }) : void 0;
206575
+ return { ...base2, outcome: "pass", durationMs: Date.now() - start, ...evidencePath ? { evidencePath } : {} };
205879
206576
  } finally {
205880
206577
  sandbox.cleanup();
205881
206578
  }
@@ -205900,7 +206597,7 @@ function toRecord(index, argv, stdin, repeat3, iterationsRun, capture, normText)
205900
206597
 
205901
206598
  // packages/guard-runner/dist/doc-index.js
205902
206599
  init_zod();
205903
- import fs55 from "node:fs";
206600
+ import fs56 from "node:fs";
205904
206601
  import path62 from "node:path";
205905
206602
 
205906
206603
  // packages/guard-runner/dist/section-index.js
@@ -206063,10 +206760,10 @@ function resolveBinding(index, anchor, fingerprint2) {
206063
206760
  var CorpusShape = external_exports.object({ docs: external_exports.array(external_exports.object({ ref: external_exports.string() }).passthrough()).optional() }).passthrough();
206064
206761
  function corpusKeptDocs(repoRoot5) {
206065
206762
  const file = path62.join(repoRoot5, ".truecourse", "specs", "corpus.json");
206066
- if (!fs55.existsSync(file))
206763
+ if (!fs56.existsSync(file))
206067
206764
  return [];
206068
206765
  try {
206069
- const parsed = CorpusShape.safeParse(JSON.parse(fs55.readFileSync(file, "utf-8")));
206766
+ const parsed = CorpusShape.safeParse(JSON.parse(fs56.readFileSync(file, "utf-8")));
206070
206767
  if (!parsed.success)
206071
206768
  return [];
206072
206769
  return (parsed.data.docs ?? []).map((d3) => d3.ref);
@@ -206082,24 +206779,24 @@ function indexRepoDocs(repoRoot5, boundDocs) {
206082
206779
  const missing = /* @__PURE__ */ new Set();
206083
206780
  for (const doc of wanted) {
206084
206781
  const abs = path62.resolve(repoRoot5, doc);
206085
- if (!fs55.existsSync(abs) || !fs55.statSync(abs).isFile()) {
206782
+ if (!fs56.existsSync(abs) || !fs56.statSync(abs).isFile()) {
206086
206783
  missing.add(doc);
206087
206784
  continue;
206088
206785
  }
206089
- indexes.set(doc, buildDocSectionIndex(doc, fs55.readFileSync(abs, "utf-8")));
206786
+ indexes.set(doc, buildDocSectionIndex(doc, fs56.readFileSync(abs, "utf-8")));
206090
206787
  }
206091
206788
  return { indexes, missing };
206092
206789
  }
206093
206790
 
206094
206791
  // packages/guard-runner/dist/manifest.js
206095
206792
  init_dist5();
206096
- import fs56 from "node:fs";
206793
+ import fs57 from "node:fs";
206097
206794
  function readManifest2(repoRoot5) {
206098
206795
  const file = manifestPath2(repoRoot5);
206099
- if (!fs56.existsSync(file))
206796
+ if (!fs57.existsSync(file))
206100
206797
  return null;
206101
206798
  try {
206102
- const parsed = GuardManifestSchema.safeParse(JSON.parse(fs56.readFileSync(file, "utf-8")));
206799
+ const parsed = GuardManifestSchema.safeParse(JSON.parse(fs57.readFileSync(file, "utf-8")));
206103
206800
  return parsed.success ? parsed.data : null;
206104
206801
  } catch {
206105
206802
  return null;
@@ -206168,13 +206865,15 @@ async function runGuard(opts) {
206168
206865
  settled += 1;
206169
206866
  opts.onScenarioSettled?.(settled, selected.length, result);
206170
206867
  }
206868
+ const capturePassEvidence = opts.persist !== false;
206171
206869
  const executed = await mapWithConcurrency(executable, concurrency, async ({ scenario, resolution }) => {
206172
206870
  const outcome = await runScenario(scenario, {
206173
206871
  repoRoot: repoRoot5,
206174
206872
  runId,
206175
206873
  resolvedEntry,
206176
206874
  recipeEnv: loaded.recipe.env,
206177
- stepTimeoutMs
206875
+ stepTimeoutMs,
206876
+ capturePassEvidence
206178
206877
  });
206179
206878
  const result = resolution.kind === "remap" ? { ...outcome, remappedTo: resolution.section.anchor } : outcome;
206180
206879
  settled += 1;
@@ -206273,13 +206972,13 @@ async function mapWithConcurrency(items, limit, fn) {
206273
206972
 
206274
206973
  // packages/guard-runner/dist/decisions.js
206275
206974
  init_dist5();
206276
- import fs57 from "node:fs";
206975
+ import fs58 from "node:fs";
206277
206976
  function readGuardDecisions(repoRoot5) {
206278
206977
  const file = guardDecisionsPath(repoRoot5);
206279
- if (!fs57.existsSync(file))
206978
+ if (!fs58.existsSync(file))
206280
206979
  return EMPTY_GUARD_DECISIONS;
206281
206980
  try {
206282
- const parsed = GuardDecisionsSchema.safeParse(JSON.parse(fs57.readFileSync(file, "utf-8")));
206981
+ const parsed = GuardDecisionsSchema.safeParse(JSON.parse(fs58.readFileSync(file, "utf-8")));
206283
206982
  return parsed.success ? parsed.data : EMPTY_GUARD_DECISIONS;
206284
206983
  } catch {
206285
206984
  return EMPTY_GUARD_DECISIONS;
@@ -206291,7 +206990,7 @@ init_dist5();
206291
206990
 
206292
206991
  // packages/guard-generator/dist/section-plan.js
206293
206992
  init_zod();
206294
- import fs58 from "node:fs";
206993
+ import fs59 from "node:fs";
206295
206994
  import path63 from "node:path";
206296
206995
  import { createHash as createHash15 } from "node:crypto";
206297
206996
  init_dist5();
@@ -206609,10 +207308,10 @@ var CorpusShape2 = external_exports.object({
206609
207308
  function readCorpusAreaTags(repoRoot5) {
206610
207309
  const file = path63.join(repoRoot5, ".truecourse", "specs", "corpus.json");
206611
207310
  const map2 = /* @__PURE__ */ new Map();
206612
- if (!fs58.existsSync(file))
207311
+ if (!fs59.existsSync(file))
206613
207312
  return map2;
206614
207313
  try {
206615
- const parsed = CorpusShape2.safeParse(JSON.parse(fs58.readFileSync(file, "utf-8")));
207314
+ const parsed = CorpusShape2.safeParse(JSON.parse(fs59.readFileSync(file, "utf-8")));
206616
207315
  if (!parsed.success)
206617
207316
  return map2;
206618
207317
  for (const d3 of parsed.data.docs ?? [])
@@ -206631,17 +207330,17 @@ function generationInputsHash(fingerprint2, recipeFingerprint) {
206631
207330
  ].join("\0")).digest("hex");
206632
207331
  }
206633
207332
  function hasGuardUniverse(repoRoot5) {
206634
- return fs58.existsSync(path63.join(repoRoot5, ".truecourse", "specs", "corpus.json"));
207333
+ return fs59.existsSync(path63.join(repoRoot5, ".truecourse", "specs", "corpus.json"));
206635
207334
  }
206636
207335
  function planGuardWork(repoRoot5, recipeFingerprint) {
206637
207336
  const recipeFp = recipeFingerprint ?? computeRecipeFingerprint(repoRoot5);
206638
- const recipeMissing = !fs58.existsSync(recipePath(repoRoot5));
207337
+ const recipeMissing = !fs59.existsSync(recipePath(repoRoot5));
206639
207338
  const hasUniverse = hasGuardUniverse(repoRoot5);
206640
207339
  const { indexes } = indexRepoDocs(repoRoot5, []);
206641
207340
  const areaTags = readCorpusAreaTags(repoRoot5);
206642
207341
  const sections = [];
206643
207342
  for (const [doc, index] of indexes) {
206644
- const texts = extractSectionTexts(doc, fs58.readFileSync(path63.resolve(repoRoot5, doc), "utf-8"));
207343
+ const texts = extractSectionTexts(doc, fs59.readFileSync(path63.resolve(repoRoot5, doc), "utf-8"));
206645
207344
  for (const s of index.sections) {
206646
207345
  const t2 = texts.get(s.anchor);
206647
207346
  sections.push({
@@ -206686,7 +207385,7 @@ function collectWorkDocs(repoRoot5, plan) {
206686
207385
  }
206687
207386
  return [...byDoc].map(([doc, sections]) => ({
206688
207387
  doc,
206689
- content: fs58.readFileSync(path63.resolve(repoRoot5, doc), "utf-8"),
207388
+ content: fs59.readFileSync(path63.resolve(repoRoot5, doc), "utf-8"),
206690
207389
  sections
206691
207390
  }));
206692
207391
  }
@@ -207073,7 +207772,7 @@ function truncate3(text4) {
207073
207772
  }
207074
207773
 
207075
207774
  // packages/guard-generator/dist/recipe-discovery.js
207076
- import fs59 from "node:fs";
207775
+ import fs60 from "node:fs";
207077
207776
  import os13 from "node:os";
207078
207777
  import path65 from "node:path";
207079
207778
  import { createHash as createHash18 } from "node:crypto";
@@ -207129,8 +207828,8 @@ async function discoverRecipe(repoRoot5, runner) {
207129
207828
  ...proposal.env ? { env: proposal.env } : {}
207130
207829
  };
207131
207830
  const target = recipePath(repoRoot5);
207132
- fs59.mkdirSync(path65.dirname(target), { recursive: true });
207133
- fs59.writeFileSync(target, JSON.stringify(recipe, null, 2) + "\n");
207831
+ fs60.mkdirSync(path65.dirname(target), { recursive: true });
207832
+ fs60.writeFileSync(target, JSON.stringify(recipe, null, 2) + "\n");
207134
207833
  return {
207135
207834
  status: "discovered",
207136
207835
  recipe,
@@ -207161,13 +207860,13 @@ async function proposeRecipeWithReask(input, runner) {
207161
207860
  }
207162
207861
  function readDiscoveryInputs(repoRoot5) {
207163
207862
  const pkgPath = path65.join(repoRoot5, "package.json");
207164
- const packageJson = fs59.existsSync(pkgPath) ? fs59.readFileSync(pkgPath, "utf-8") : "(no package.json)";
207165
- const presentInputs = DISCOVERY_INPUTS.filter((f2) => fs59.existsSync(path65.join(repoRoot5, f2)));
207863
+ const packageJson = fs60.existsSync(pkgPath) ? fs60.readFileSync(pkgPath, "utf-8") : "(no package.json)";
207864
+ const presentInputs = DISCOVERY_INPUTS.filter((f2) => fs60.existsSync(path65.join(repoRoot5, f2)));
207166
207865
  return { packageJson, presentInputs };
207167
207866
  }
207168
207867
  async function probeEntry(repoRoot5, entry) {
207169
207868
  const resolved = resolveEntry(repoRoot5, entry);
207170
- const cwd = fs59.mkdtempSync(path65.join(os13.tmpdir(), "tc-guard-probe-"));
207869
+ const cwd = fs60.mkdtempSync(path65.join(os13.tmpdir(), "tc-guard-probe-"));
207171
207870
  try {
207172
207871
  for (const args of [["--help"], []]) {
207173
207872
  const capture = await executeStep({
@@ -207184,7 +207883,7 @@ async function probeEntry(repoRoot5, entry) {
207184
207883
  reason: `entrypoint ${JSON.stringify(entry)} did not answer to \`--help\` or a bare invocation`
207185
207884
  };
207186
207885
  } finally {
207187
- fs59.rmSync(cwd, { recursive: true, force: true });
207886
+ fs60.rmSync(cwd, { recursive: true, force: true });
207188
207887
  }
207189
207888
  }
207190
207889
 
@@ -207231,7 +207930,7 @@ async function birthValidate(repoRoot5, candidates, opts = {}) {
207231
207930
  }
207232
207931
 
207233
207932
  // packages/guard-generator/dist/serialize.js
207234
- import fs60 from "node:fs";
207933
+ import fs61 from "node:fs";
207235
207934
  import path66 from "node:path";
207236
207935
  init_dist5();
207237
207936
  function anchorLeaf(anchor) {
@@ -207274,15 +207973,15 @@ function scenarioFileIndex(repoRoot5) {
207274
207973
  const root2 = scenariosDir(repoRoot5);
207275
207974
  const map2 = /* @__PURE__ */ new Map();
207276
207975
  const walk20 = (dir) => {
207277
- if (!fs60.existsSync(dir))
207976
+ if (!fs61.existsSync(dir))
207278
207977
  return;
207279
- for (const entry of fs60.readdirSync(dir, { withFileTypes: true })) {
207978
+ for (const entry of fs61.readdirSync(dir, { withFileTypes: true })) {
207280
207979
  const full = path66.join(dir, entry.name);
207281
207980
  if (entry.isDirectory())
207282
207981
  walk20(full);
207283
207982
  else if (entry.isFile() && /\.ya?ml$/i.test(entry.name)) {
207284
207983
  try {
207285
- const doc = jsYaml.load(fs60.readFileSync(full, "utf-8"));
207984
+ const doc = jsYaml.load(fs61.readFileSync(full, "utf-8"));
207286
207985
  if (doc && typeof doc.id === "string")
207287
207986
  map2.set(doc.id, full);
207288
207987
  } catch {
@@ -207301,17 +208000,17 @@ function serializeScenarioYaml(scenario) {
207301
208000
  }
207302
208001
  function writeScenarioFile(repoRoot5, slug2, scenario) {
207303
208002
  const dir = path66.join(scenariosDir(repoRoot5), slug2);
207304
- fs60.mkdirSync(dir, { recursive: true });
208003
+ fs61.mkdirSync(dir, { recursive: true });
207305
208004
  const file = path66.join(dir, `${scenario.id}.yaml`);
207306
- fs60.writeFileSync(file, serializeScenarioYaml(scenario));
208005
+ fs61.writeFileSync(file, serializeScenarioYaml(scenario));
207307
208006
  return path66.relative(repoRoot5, file);
207308
208007
  }
207309
208008
  function deleteScenarioFiles(repoRoot5, ids) {
207310
208009
  const index = scenarioFileIndex(repoRoot5);
207311
208010
  for (const id of ids) {
207312
208011
  const file = index.get(id);
207313
- if (file && fs60.existsSync(file))
207314
- fs60.rmSync(file);
208012
+ if (file && fs61.existsSync(file))
208013
+ fs61.rmSync(file);
207315
208014
  }
207316
208015
  }
207317
208016
 
@@ -208016,7 +208715,7 @@ init_context_router();
208016
208715
 
208017
208716
  // packages/core/dist/services/llm/model-prices.js
208018
208717
  init_paths();
208019
- import fs61 from "node:fs";
208718
+ import fs62 from "node:fs";
208020
208719
  import path67 from "node:path";
208021
208720
  var OPENROUTER_URL = "https://openrouter.ai/api/v1/models";
208022
208721
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -208037,7 +208736,7 @@ function cacheFilePath() {
208037
208736
  }
208038
208737
  function readCache7() {
208039
208738
  try {
208040
- const t2 = JSON.parse(fs61.readFileSync(cacheFilePath(), "utf-8"));
208739
+ const t2 = JSON.parse(fs62.readFileSync(cacheFilePath(), "utf-8"));
208041
208740
  if (t2 && t2.tiers && t2.byId && typeof t2.fetchedAt === "number")
208042
208741
  return t2;
208043
208742
  } catch {
@@ -208046,8 +208745,8 @@ function readCache7() {
208046
208745
  }
208047
208746
  function writeCache7(t2) {
208048
208747
  try {
208049
- fs61.mkdirSync(path67.dirname(cacheFilePath()), { recursive: true });
208050
- fs61.writeFileSync(cacheFilePath(), JSON.stringify(t2));
208748
+ fs62.mkdirSync(path67.dirname(cacheFilePath()), { recursive: true });
208749
+ fs62.writeFileSync(cacheFilePath(), JSON.stringify(t2));
208051
208750
  } catch {
208052
208751
  }
208053
208752
  }
@@ -208426,7 +209125,7 @@ function changedSubject(total, changed, noun) {
208426
209125
 
208427
209126
  // packages/core/dist/commands/spec-in-process.js
208428
209127
  init_dist8();
208429
- import fs66 from "node:fs";
209128
+ import fs67 from "node:fs";
208430
209129
  import path72 from "node:path";
208431
209130
  init_git();
208432
209131
  import { createHash as createHash20, randomUUID as randomUUID27 } from "node:crypto";
@@ -208434,7 +209133,7 @@ import { createHash as createHash20, randomUUID as randomUUID27 } from "node:cry
208434
209133
  // packages/core/dist/lib/verify-store.js
208435
209134
  init_atomic_write();
208436
209135
  init_analysis_store();
208437
- import fs62 from "node:fs";
209136
+ import fs63 from "node:fs";
208438
209137
  import path68 from "node:path";
208439
209138
 
208440
209139
  // packages/core/dist/types/verify-snapshot.js
@@ -208533,7 +209232,7 @@ var FileVerifyStore = class {
208533
209232
  const file = verifyLatestPath(repoPath);
208534
209233
  let mtime;
208535
209234
  try {
208536
- mtime = fs62.statSync(file).mtimeMs;
209235
+ mtime = fs63.statSync(file).mtimeMs;
208537
209236
  } catch (err) {
208538
209237
  if (err.code === "ENOENT") {
208539
209238
  latestCache2.delete(repoPath);
@@ -208544,7 +209243,7 @@ var FileVerifyStore = class {
208544
209243
  const cached = latestCache2.get(repoPath);
208545
209244
  if (cached && cached.mtime === mtime)
208546
209245
  return cached.data;
208547
- const data = JSON.parse(fs62.readFileSync(file, "utf-8"));
209246
+ const data = JSON.parse(fs63.readFileSync(file, "utf-8"));
208548
209247
  latestCache2.set(repoPath, { mtime, data });
208549
209248
  return data;
208550
209249
  }
@@ -208554,7 +209253,7 @@ var FileVerifyStore = class {
208554
209253
  }
208555
209254
  async deleteVerifyLatest(repoPath) {
208556
209255
  try {
208557
- fs62.unlinkSync(verifyLatestPath(repoPath));
209256
+ fs63.unlinkSync(verifyLatestPath(repoPath));
208558
209257
  } catch (err) {
208559
209258
  if (err.code !== "ENOENT")
208560
209259
  throw err;
@@ -208568,21 +209267,21 @@ var FileVerifyStore = class {
208568
209267
  }
208569
209268
  async readVerifyRun(repoPath, filename) {
208570
209269
  const file = verifyRunPath(repoPath, filename);
208571
- if (!fs62.existsSync(file))
209270
+ if (!fs63.existsSync(file))
208572
209271
  return null;
208573
- return JSON.parse(fs62.readFileSync(file, "utf-8"));
209272
+ return JSON.parse(fs63.readFileSync(file, "utf-8"));
208574
209273
  }
208575
209274
  async listVerifyRuns(repoPath) {
208576
209275
  const dir = runsDir(repoPath);
208577
- if (!fs62.existsSync(dir))
209276
+ if (!fs63.existsSync(dir))
208578
209277
  return [];
208579
- return fs62.readdirSync(dir).filter((n) => n.endsWith(".json")).sort();
209278
+ return fs63.readdirSync(dir).filter((n) => n.endsWith(".json")).sort();
208580
209279
  }
208581
209280
  async readVerifyHistory(repoPath) {
208582
209281
  const file = verifyHistoryPath(repoPath);
208583
- if (!fs62.existsSync(file))
209282
+ if (!fs63.existsSync(file))
208584
209283
  return { runs: [] };
208585
- return JSON.parse(fs62.readFileSync(file, "utf-8"));
209284
+ return JSON.parse(fs63.readFileSync(file, "utf-8"));
208586
209285
  }
208587
209286
  async appendVerifyHistory(repoPath, entry) {
208588
209287
  const history = await this.readVerifyHistory(repoPath);
@@ -208595,7 +209294,7 @@ var FileVerifyStore = class {
208595
209294
  if (!entry)
208596
209295
  return false;
208597
209296
  try {
208598
- fs62.unlinkSync(verifyRunPath(repoPath, entry.filename));
209297
+ fs63.unlinkSync(verifyRunPath(repoPath, entry.filename));
208599
209298
  } catch (err) {
208600
209299
  if (err.code !== "ENOENT")
208601
209300
  throw err;
@@ -208617,16 +209316,16 @@ var FileVerifyStore = class {
208617
209316
  }
208618
209317
  async readVerifyDiff(repoPath, scope) {
208619
209318
  const file = verifyDiffPath(repoPath, scope);
208620
- if (!fs62.existsSync(file))
209319
+ if (!fs63.existsSync(file))
208621
209320
  return null;
208622
- return JSON.parse(fs62.readFileSync(file, "utf-8"));
209321
+ return JSON.parse(fs63.readFileSync(file, "utf-8"));
208623
209322
  }
208624
209323
  async writeVerifyDiff(repoPath, diff, scope) {
208625
209324
  atomicWriteJson(verifyDiffPath(repoPath, scope), diff);
208626
209325
  }
208627
209326
  async deleteVerifyDiff(repoPath, scope) {
208628
209327
  try {
208629
- fs62.unlinkSync(verifyDiffPath(repoPath, scope));
209328
+ fs63.unlinkSync(verifyDiffPath(repoPath, scope));
208630
209329
  } catch (err) {
208631
209330
  if (err.code !== "ENOENT")
208632
209331
  throw err;
@@ -208660,7 +209359,7 @@ async function repoRef(repoRoot5, commitOverride) {
208660
209359
  }
208661
209360
 
208662
209361
  // packages/core/dist/lib/contract-store.js
208663
- import fs63 from "node:fs";
209362
+ import fs64 from "node:fs";
208664
209363
  import path69 from "node:path";
208665
209364
  var KIND_REL = {
208666
209365
  contracts: [".truecourse", "contracts"],
@@ -208671,7 +209370,7 @@ function countTcFiles(dir) {
208671
209370
  let n = 0;
208672
209371
  let entries;
208673
209372
  try {
208674
- entries = fs63.readdirSync(dir, { withFileTypes: true });
209373
+ entries = fs64.readdirSync(dir, { withFileTypes: true });
208675
209374
  } catch {
208676
209375
  return 0;
208677
209376
  }
@@ -208688,7 +209387,7 @@ function walkTcRel(dir, excludeInferred) {
208688
209387
  const walk20 = (rel) => {
208689
209388
  let entries;
208690
209389
  try {
208691
- entries = fs63.readdirSync(path69.join(dir, rel), { withFileTypes: true });
209390
+ entries = fs64.readdirSync(path69.join(dir, rel), { withFileTypes: true });
208692
209391
  } catch {
208693
209392
  return;
208694
209393
  }
@@ -208726,13 +209425,13 @@ var FileContractStore = class {
208726
209425
  }
208727
209426
  async loadContracts(ref, kind) {
208728
209427
  const dir = path69.join(ref.repoKey, ...KIND_REL[kind]);
208729
- if (!fs63.existsSync(dir))
209428
+ if (!fs64.existsSync(dir))
208730
209429
  return null;
208731
209430
  return { dir, cleanup: async () => {
208732
209431
  } };
208733
209432
  }
208734
209433
  async hasContracts(ref, kind) {
208735
- return fs63.existsSync(path69.join(ref.repoKey, ...KIND_REL[kind]));
209434
+ return fs64.existsSync(path69.join(ref.repoKey, ...KIND_REL[kind]));
208736
209435
  }
208737
209436
  // The file impl reads the live repo tree, which is whatever is checked out —
208738
209437
  // there is no per-commit history, so `commitSha` is ignored (OSS is latest).
@@ -208741,21 +209440,21 @@ var FileContractStore = class {
208741
209440
  }
208742
209441
  async readContractFile(repoKey, kind, relPath, _commitSha) {
208743
209442
  const dest = safeResolve(path69.join(repoKey, ...KIND_REL[kind]), relPath);
208744
- if (!dest || !fs63.existsSync(dest) || !fs63.statSync(dest).isFile())
209443
+ if (!dest || !fs64.existsSync(dest) || !fs64.statSync(dest).isFile())
208745
209444
  return null;
208746
- return fs63.readFileSync(dest, "utf-8");
209445
+ return fs64.readFileSync(dest, "utf-8");
208747
209446
  }
208748
209447
  async putContractFile(ref, kind, relPath, content) {
208749
209448
  const dest = safeResolve(path69.join(ref.repoKey, ...KIND_REL[kind]), relPath);
208750
209449
  if (!dest)
208751
209450
  throw new Error(`[contract-store] unsafe contract path: ${relPath}`);
208752
- fs63.mkdirSync(path69.dirname(dest), { recursive: true });
208753
- fs63.writeFileSync(dest, content);
209451
+ fs64.mkdirSync(path69.dirname(dest), { recursive: true });
209452
+ fs64.writeFileSync(dest, content);
208754
209453
  }
208755
209454
  async deleteContractFile(ref, kind, relPath) {
208756
209455
  const dest = safeResolve(path69.join(ref.repoKey, ...KIND_REL[kind]), relPath);
208757
209456
  if (dest)
208758
- fs63.rmSync(dest, { force: true });
209457
+ fs64.rmSync(dest, { force: true });
208759
209458
  }
208760
209459
  // OSS/local has no workspace concept (mirrors the spec store). Writing throws
208761
209460
  // (fail loud — a caller that reached here is mis-wired); reads are empty so an
@@ -208782,7 +209481,7 @@ var loadWorkspaceContracts = (ref, kind) => active8.loadWorkspaceContracts(ref,
208782
209481
  var contractsMaterializeInPlace = () => active8.materializesInPlace;
208783
209482
 
208784
209483
  // packages/core/dist/lib/spec-store.js
208785
- import fs64 from "node:fs";
209484
+ import fs65 from "node:fs";
208786
209485
  import path70 from "node:path";
208787
209486
  function specPath(repoKey, artifact) {
208788
209487
  return path70.join(repoKey, ".truecourse", "specs", `${artifact}.json`);
@@ -208798,18 +209497,18 @@ var FileSpecStore = class {
208798
209497
  throw new Error(PR_DECISIONS_FILE_ERROR);
208799
209498
  }
208800
209499
  const file = specPath(ref.repoKey, artifact);
208801
- fs64.mkdirSync(path70.dirname(file), { recursive: true });
208802
- fs64.writeFileSync(file, JSON.stringify(json2, null, 2) + "\n", "utf-8");
209500
+ fs65.mkdirSync(path70.dirname(file), { recursive: true });
209501
+ fs65.writeFileSync(file, JSON.stringify(json2, null, 2) + "\n", "utf-8");
208803
209502
  }
208804
209503
  async loadSpec(ref, artifact) {
208805
209504
  if (artifact === "decisions" && isPrDecisionsRef(ref.commitSha)) {
208806
209505
  throw new Error(PR_DECISIONS_FILE_ERROR);
208807
209506
  }
208808
209507
  const file = specPath(ref.repoKey, artifact);
208809
- if (!fs64.existsSync(file))
209508
+ if (!fs65.existsSync(file))
208810
209509
  return null;
208811
209510
  try {
208812
- return JSON.parse(fs64.readFileSync(file, "utf-8"));
209511
+ return JSON.parse(fs65.readFileSync(file, "utf-8"));
208813
209512
  } catch {
208814
209513
  return null;
208815
209514
  }
@@ -208819,8 +209518,8 @@ var FileSpecStore = class {
208819
209518
  throw new Error(PR_DECISIONS_FILE_ERROR);
208820
209519
  }
208821
209520
  const file = specPath(ref.repoKey, artifact);
208822
- if (fs64.existsSync(file))
208823
- fs64.rmSync(file);
209521
+ if (fs65.existsSync(file))
209522
+ fs65.rmSync(file);
208824
209523
  }
208825
209524
  // The file impl is single-document-per-repo, so "latest" === read the file.
208826
209525
  async loadLatest(repoKey, artifact) {
@@ -208847,7 +209546,7 @@ var loadLatestSpec = (repoKey, artifact) => active9.loadLatest(repoKey, artifact
208847
209546
  var specsMaterializeInPlace = () => active9.materializesInPlace;
208848
209547
 
208849
209548
  // packages/core/dist/lib/inferred-action-store.js
208850
- import fs65 from "node:fs";
209549
+ import fs66 from "node:fs";
208851
209550
  import path71 from "node:path";
208852
209551
  function actionsFile(repoKey) {
208853
209552
  return path71.join(repoKey, ".truecourse", "specs", "inferred-actions.json");
@@ -208855,15 +209554,15 @@ function actionsFile(repoKey) {
208855
209554
  var FileInferredActionStore = class {
208856
209555
  read(repoKey) {
208857
209556
  try {
208858
- return JSON.parse(fs65.readFileSync(actionsFile(repoKey), "utf-8"));
209557
+ return JSON.parse(fs66.readFileSync(actionsFile(repoKey), "utf-8"));
208859
209558
  } catch {
208860
209559
  return [];
208861
209560
  }
208862
209561
  }
208863
209562
  write(repoKey, actions) {
208864
209563
  const file = actionsFile(repoKey);
208865
- fs65.mkdirSync(path71.dirname(file), { recursive: true });
208866
- fs65.writeFileSync(file, JSON.stringify(actions, null, 2) + "\n", "utf-8");
209564
+ fs66.mkdirSync(path71.dirname(file), { recursive: true });
209565
+ fs66.writeFileSync(file, JSON.stringify(actions, null, 2) + "\n", "utf-8");
208867
209566
  }
208868
209567
  async setAction(repoKey, action) {
208869
209568
  const next = this.read(repoKey).filter((a) => !(a.kind === action.kind && a.identity === action.identity));
@@ -208998,7 +209697,7 @@ function generatedMarkerPath(repoRoot5) {
208998
209697
  }
208999
209698
  function stampGeneratedMarker(repoRoot5, summary) {
209000
209699
  const file = generatedMarkerPath(repoRoot5);
209001
- fs66.mkdirSync(path72.dirname(file), { recursive: true });
209700
+ fs67.mkdirSync(path72.dirname(file), { recursive: true });
209002
209701
  const body = {
209003
209702
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
209004
209703
  written: summary?.written ?? 0,
@@ -209006,7 +209705,7 @@ function stampGeneratedMarker(repoRoot5, summary) {
209006
209705
  validationIssues: summary?.validationIssues ?? [],
209007
209706
  enumerateFailures: summary?.enumerateFailures ?? []
209008
209707
  };
209009
- fs66.writeFileSync(file, JSON.stringify(body, null, 2) + "\n");
209708
+ fs67.writeFileSync(file, JSON.stringify(body, null, 2) + "\n");
209010
209709
  }
209011
209710
  function resolveCurateModels(repoRoot5) {
209012
209711
  return {
@@ -209131,19 +209830,19 @@ async function curateInProcess(repoRoot5, options = {}) {
209131
209830
  }
209132
209831
  function buildPriorContracts(repoRoot5) {
209133
209832
  const dir = path72.join(repoRoot5, ".truecourse", "contracts");
209134
- if (!fs66.existsSync(dir))
209833
+ if (!fs67.existsSync(dir))
209135
209834
  return void 0;
209136
209835
  const targets = [];
209137
209836
  const bodyByKey = /* @__PURE__ */ new Map();
209138
209837
  const walk20 = (d3) => {
209139
- for (const e of fs66.readdirSync(d3, { withFileTypes: true })) {
209838
+ for (const e of fs67.readdirSync(d3, { withFileTypes: true })) {
209140
209839
  if (e.isDirectory()) {
209141
209840
  if (e.name === "_inferred")
209142
209841
  continue;
209143
209842
  walk20(path72.join(d3, e.name));
209144
209843
  } else if (e.name.endsWith(".tc")) {
209145
209844
  const abs = path72.join(d3, e.name);
209146
- const src = fs66.readFileSync(abs, "utf-8");
209845
+ const src = fs67.readFileSync(abs, "utf-8");
209147
209846
  try {
209148
209847
  const file = parser_ohm_exports.parseTcFile(path72.relative(dir, abs), src);
209149
209848
  for (const key4 of resolver_exports.resolve([file]).index.keys()) {
@@ -209316,7 +210015,7 @@ async function withContracts(repoRoot5, options, tracker, fn) {
209316
210015
  try {
209317
210016
  let recorded;
209318
210017
  if (options.contractsDir) {
209319
- if (!fs66.existsSync(options.contractsDir)) {
210018
+ if (!fs67.existsSync(options.contractsDir)) {
209320
210019
  const err = new Error(`Contracts directory not found at ${options.contractsDir}. Run \`truecourse contracts generate\` first.`);
209321
210020
  tracker?.error("load", err.message);
209322
210021
  throw err;
@@ -209405,7 +210104,7 @@ async function verifyInProcess(repoRoot5, options = {}) {
209405
210104
  bySeverity: summary.bySeverity
209406
210105
  });
209407
210106
  await deleteVerifyDiff(storeKey);
209408
- fs66.rmSync(legacyVerifyStatePath(repoRoot5), { force: true });
210107
+ fs67.rmSync(legacyVerifyStatePath(repoRoot5), { force: true });
209409
210108
  }
209410
210109
  const state = {
209411
210110
  verifiedAt,
@@ -209623,7 +210322,7 @@ async function persistInferred(repoRoot5, options, contractsDir, codeDir, starte
209623
210322
  }
209624
210323
  function autodetectCodeDir(repoRoot5) {
209625
210324
  const codeSubdir = path72.join(repoRoot5, "code");
209626
- if (fs66.existsSync(codeSubdir) && fs66.statSync(codeSubdir).isDirectory()) {
210325
+ if (fs67.existsSync(codeSubdir) && fs67.statSync(codeSubdir).isDirectory()) {
209627
210326
  return codeSubdir;
209628
210327
  }
209629
210328
  return repoRoot5;
@@ -209944,7 +210643,7 @@ async function runContractsGenerate(options = {}) {
209944
210643
  async function runContractsList(options = {}) {
209945
210644
  const repoRoot5 = options.cwd ?? process.cwd();
209946
210645
  const contractsDir = path73.join(repoRoot5, ".truecourse", "contracts");
209947
- if (!fs67.existsSync(contractsDir)) {
210646
+ if (!fs68.existsSync(contractsDir)) {
209948
210647
  O2.info("No contracts found. Run `truecourse contracts generate` first.");
209949
210648
  return;
209950
210649
  }
@@ -209952,12 +210651,12 @@ async function runContractsList(options = {}) {
209952
210651
  const fileNodes = [];
209953
210652
  let parseErrors = 0;
209954
210653
  const visit = (dir) => {
209955
- for (const entry of fs67.readdirSync(dir, { withFileTypes: true })) {
210654
+ for (const entry of fs68.readdirSync(dir, { withFileTypes: true })) {
209956
210655
  const full = path73.join(dir, entry.name);
209957
210656
  if (entry.isDirectory()) visit(full);
209958
210657
  else if (entry.isFile() && entry.name.endsWith(".tc")) {
209959
210658
  try {
209960
- fileNodes.push(parserOhm.parseTcFile(full, fs67.readFileSync(full, "utf-8")));
210659
+ fileNodes.push(parserOhm.parseTcFile(full, fs68.readFileSync(full, "utf-8")));
209961
210660
  } catch {
209962
210661
  parseErrors += 1;
209963
210662
  }
@@ -209997,7 +210696,7 @@ async function runContractsList(options = {}) {
209997
210696
  async function runContractsValidate(options = {}) {
209998
210697
  const repoRoot5 = options.cwd ?? process.cwd();
209999
210698
  const contractsDir = path73.join(repoRoot5, ".truecourse", "contracts");
210000
- if (!fs67.existsSync(contractsDir)) {
210699
+ if (!fs68.existsSync(contractsDir)) {
210001
210700
  O2.error("No .truecourse/contracts/ directory found.");
210002
210701
  process.exit(1);
210003
210702
  }
@@ -210005,12 +210704,12 @@ async function runContractsValidate(options = {}) {
210005
210704
  const fileNodes = [];
210006
210705
  const issues = [];
210007
210706
  const visit = (dir) => {
210008
- for (const entry of fs67.readdirSync(dir, { withFileTypes: true })) {
210707
+ for (const entry of fs68.readdirSync(dir, { withFileTypes: true })) {
210009
210708
  const full = path73.join(dir, entry.name);
210010
210709
  if (entry.isDirectory()) visit(full);
210011
210710
  else if (entry.isFile() && entry.name.endsWith(".tc")) {
210012
210711
  try {
210013
- fileNodes.push(parserOhm.parseTcFile(full, fs67.readFileSync(full, "utf-8")));
210712
+ fileNodes.push(parserOhm.parseTcFile(full, fs68.readFileSync(full, "utf-8")));
210014
210713
  } catch (e) {
210015
210714
  issues.push(`${path73.relative(repoRoot5, full)}: parse error: ${e instanceof Error ? e.message : e}`);
210016
210715
  }
@@ -210253,7 +210952,7 @@ async function runInfer(opts = {}) {
210253
210952
 
210254
210953
  // tools/cli/src/commands/spec-conflicts.ts
210255
210954
  init_dist4();
210256
- import fs68 from "node:fs";
210955
+ import fs69 from "node:fs";
210257
210956
  import path75 from "node:path";
210258
210957
  var root = (opts) => opts.cwd ?? process.cwd();
210259
210958
  var base = (ref) => ref.split("/").pop() ?? ref;
@@ -210301,7 +211000,7 @@ async function runSpecConflictsList(opts = {}) {
210301
211000
  function excerpt(repoRoot5, ref, note, max = 20) {
210302
211001
  let text4;
210303
211002
  try {
210304
- text4 = fs68.readFileSync(path75.join(repoRoot5, ref), "utf-8");
211003
+ text4 = fs69.readFileSync(path75.join(repoRoot5, ref), "utf-8");
210305
211004
  } catch {
210306
211005
  return ` (could not read ${ref})`;
210307
211006
  }
@@ -211249,7 +211948,7 @@ async function runConfigLlmShow(options = {}) {
211249
211948
 
211250
211949
  // tools/cli/src/commands/hooks.ts
211251
211950
  import { execSync as execSync4 } from "node:child_process";
211252
- import fs69 from "node:fs";
211951
+ import fs70 from "node:fs";
211253
211952
  import path79 from "node:path";
211254
211953
  init_dist4();
211255
211954
  init_paths();
@@ -211285,11 +211984,11 @@ function findGitDir(from) {
211285
211984
  let dir = from;
211286
211985
  while (true) {
211287
211986
  const gitPath = path79.join(dir, ".git");
211288
- if (fs69.existsSync(gitPath)) {
211289
- const stat = fs69.statSync(gitPath);
211987
+ if (fs70.existsSync(gitPath)) {
211988
+ const stat = fs70.statSync(gitPath);
211290
211989
  if (stat.isDirectory()) return gitPath;
211291
211990
  if (stat.isFile()) {
211292
- const content = fs69.readFileSync(gitPath, "utf-8").trim();
211991
+ const content = fs70.readFileSync(gitPath, "utf-8").trim();
211293
211992
  const match4 = content.match(/^gitdir:\s*(.+)$/);
211294
211993
  if (match4) return path79.resolve(dir, match4[1]);
211295
211994
  }
@@ -211302,7 +212001,7 @@ function findGitDir(from) {
211302
212001
  function findProjectRoot(from) {
211303
212002
  let dir = from;
211304
212003
  while (true) {
211305
- if (fs69.existsSync(path79.join(dir, ".git"))) return dir;
212004
+ if (fs70.existsSync(path79.join(dir, ".git"))) return dir;
211306
212005
  const parent = path79.dirname(dir);
211307
212006
  if (parent === dir) return null;
211308
212007
  dir = parent;
@@ -211310,10 +212009,10 @@ function findProjectRoot(from) {
211310
212009
  }
211311
212010
  function loadConfig(projectRoot) {
211312
212011
  const configPath = path79.join(projectRoot, ".truecourse", "hooks.yaml");
211313
- if (!fs69.existsSync(configPath)) return null;
212012
+ if (!fs70.existsSync(configPath)) return null;
211314
212013
  let parsed;
211315
212014
  try {
211316
- const raw = fs69.readFileSync(configPath, "utf-8");
212015
+ const raw = fs70.readFileSync(configPath, "utf-8");
211317
212016
  parsed = jsYaml.load(raw) || {};
211318
212017
  } catch (err) {
211319
212018
  console.error(`Error parsing ${configPath}: ${err.message}`);
@@ -211365,10 +212064,10 @@ async function runHooksInstall() {
211365
212064
  console.log(INSTALL_WARNING);
211366
212065
  }
211367
212066
  const hooksDir = path79.join(gitDir, "hooks");
211368
- fs69.mkdirSync(hooksDir, { recursive: true });
212067
+ fs70.mkdirSync(hooksDir, { recursive: true });
211369
212068
  const hookPath = path79.join(hooksDir, "pre-commit");
211370
- if (fs69.existsSync(hookPath)) {
211371
- const existing = fs69.readFileSync(hookPath, "utf-8");
212069
+ if (fs70.existsSync(hookPath)) {
212070
+ const existing = fs70.readFileSync(hookPath, "utf-8");
211372
212071
  if (!existing.includes(HOOK_IDENTIFIER)) {
211373
212072
  console.error(
211374
212073
  "Error: A pre-commit hook already exists and was not installed by TrueCourse."
@@ -211378,16 +212077,16 @@ async function runHooksInstall() {
211378
212077
  process.exit(1);
211379
212078
  }
211380
212079
  }
211381
- fs69.writeFileSync(hookPath, HOOK_SCRIPT, { mode: 493 });
212080
+ fs70.writeFileSync(hookPath, HOOK_SCRIPT, { mode: 493 });
211382
212081
  console.log("TrueCourse pre-commit hook installed.");
211383
212082
  console.log(` ${hookPath}`);
211384
212083
  const projectRoot = findProjectRoot(process.cwd());
211385
212084
  if (projectRoot) {
211386
212085
  const cfgDir = path79.join(projectRoot, ".truecourse");
211387
212086
  const cfgPath = path79.join(cfgDir, "hooks.yaml");
211388
- if (!fs69.existsSync(cfgPath)) {
211389
- fs69.mkdirSync(cfgDir, { recursive: true });
211390
- fs69.writeFileSync(cfgPath, HOOKS_YAML_TEMPLATE);
212087
+ if (!fs70.existsSync(cfgPath)) {
212088
+ fs70.mkdirSync(cfgDir, { recursive: true });
212089
+ fs70.writeFileSync(cfgPath, HOOKS_YAML_TEMPLATE);
211391
212090
  console.log(` ${cfgPath} (starter config \u2014 edit to customize, commit to share with the team)`);
211392
212091
  }
211393
212092
  }
@@ -211399,16 +212098,16 @@ function runHooksUninstall() {
211399
212098
  process.exit(1);
211400
212099
  }
211401
212100
  const hookPath = path79.join(gitDir, "hooks", "pre-commit");
211402
- if (!fs69.existsSync(hookPath)) {
212101
+ if (!fs70.existsSync(hookPath)) {
211403
212102
  console.log("No pre-commit hook installed.");
211404
212103
  return;
211405
212104
  }
211406
- const content = fs69.readFileSync(hookPath, "utf-8");
212105
+ const content = fs70.readFileSync(hookPath, "utf-8");
211407
212106
  if (!content.includes(HOOK_IDENTIFIER)) {
211408
212107
  console.error("Error: The pre-commit hook was not installed by TrueCourse. Leaving it in place.");
211409
212108
  process.exit(1);
211410
212109
  }
211411
- fs69.unlinkSync(hookPath);
212110
+ fs70.unlinkSync(hookPath);
211412
212111
  console.log("TrueCourse pre-commit hook removed.");
211413
212112
  }
211414
212113
  function runHooksStatus() {
@@ -211418,7 +212117,7 @@ function runHooksStatus() {
211418
212117
  process.exit(1);
211419
212118
  }
211420
212119
  const hookPath = path79.join(gitDir, "hooks", "pre-commit");
211421
- const installed = fs69.existsSync(hookPath) && fs69.readFileSync(hookPath, "utf-8").includes(HOOK_IDENTIFIER);
212120
+ const installed = fs70.existsSync(hookPath) && fs70.readFileSync(hookPath, "utf-8").includes(HOOK_IDENTIFIER);
211422
212121
  if (installed) {
211423
212122
  console.log("TrueCourse pre-commit hook: installed");
211424
212123
  console.log(` ${hookPath}`);
@@ -211527,7 +212226,13 @@ async function runHooksRun() {
211527
212226
 
211528
212227
  // tools/cli/src/index.ts
211529
212228
  var program2 = new Command();
211530
- program2.name("truecourse").version("0.7.0-next.4").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
212229
+ program2.name("truecourse").version("0.7.0-next.5").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
212230
+ function warnDiscontinued(command) {
212231
+ process.stderr.write(
212232
+ `\u26A0 \`${command}\` is discontinued in favor of \`truecourse guard\` \u2014 see the README. Planned removal: 0.8.
212233
+ `
212234
+ );
212235
+ }
211531
212236
  var dashboardCmd = program2.command("dashboard").description("Start the TrueCourse dashboard and open it in your browser").option("--reconfigure", "Re-prompt for console vs background service mode").option("--service", "Run as a background service (skips mode prompt)").option("--console", "Run in this terminal (skips mode prompt)").action(async (options) => {
211532
212237
  if (options.service && options.console) {
211533
212238
  console.error("error: --service and --console are mutually exclusive");
@@ -211581,14 +212286,17 @@ program2.command("list").description("List violations from the latest analysis")
211581
212286
  });
211582
212287
  }
211583
212288
  });
211584
- var contractsCmd = program2.command("contracts").description("Manage spec-driven contract artifacts");
212289
+ var contractsCmd = program2.command("contracts", { hidden: true }).description("Manage spec-driven contract artifacts");
211585
212290
  contractsCmd.command("generate").description("Generate .tc artifacts from the curated corpus (corpus.json)").option("--diff", "Dry run \u2014 show what would change without writing").option("-y, --yes", "Skip the pre-flight LLM cost-estimate confirmation").option("--llm-transport <mode>", "How to reach the LLM: 'cli' (spawn claude -p, default) or 'agent' (filesystem mailbox)").option("--io <dir>", "Mailbox dir for --llm-transport agent (request/response files)").action(async (options) => {
212291
+ warnDiscontinued("contracts");
211586
212292
  await runContractsGenerate({ diff: !!options.diff, yes: !!options.yes, llm: options.llmTransport, io: options.io });
211587
212293
  });
211588
212294
  contractsCmd.command("list").description("List the .tc artifacts in this repo (kind \xB7 identity \xB7 location)").option("--inferred", "Only inferred artifacts (reverse-engineered, in _inferred/)").option("--authored", "Only authored artifacts (exclude _inferred/)").action(async (options) => {
212295
+ warnDiscontinued("contracts");
211589
212296
  await runContractsList({ inferred: !!options.inferred, authored: !!options.authored });
211590
212297
  });
211591
212298
  contractsCmd.command("validate").description("Parse and resolve all .tc files, report any issues").action(async () => {
212299
+ warnDiscontinued("contracts");
211592
212300
  await runContractsValidate();
211593
212301
  });
211594
212302
  var specCmd = program2.command("spec").description("Curate scattered docs into a corpus of areas and doc relations");
@@ -211652,17 +212360,20 @@ docsCmd.command("exclude <path...>").description("Force-exclude one or more kept
211652
212360
  docsCmd.command("unexclude <path>").description("Remove a force-exclude override").action(async (docPath) => {
211653
212361
  await runSpecDocsUnexclude(docPath);
211654
212362
  });
211655
- program2.command("verify").description("Compare code against the canonical TC contracts").option("--code-dir <path>", "Override the code directory (default: auto-detect)").option("--diff", "Diff current drifts against the committed LATEST baseline").option("--stash", "Pre-approve stashing pending changes (verify committed state)").option("--no-stash", "Verify the working tree as-is without stashing").action(async (options) => {
212363
+ program2.command("verify", { hidden: true }).description("Compare code against the canonical TC contracts").option("--code-dir <path>", "Override the code directory (default: auto-detect)").option("--diff", "Diff current drifts against the committed LATEST baseline").option("--stash", "Pre-approve stashing pending changes (verify committed state)").option("--no-stash", "Verify the working tree as-is without stashing").action(async (options) => {
212364
+ warnDiscontinued("verify");
211656
212365
  await runVerify({ codeDir: options.codeDir, diff: options.diff, stash: options.stash });
211657
212366
  });
211658
- program2.command("infer").description("Reverse-engineer undocumented decisions from code into inferred contracts").option("--code-dir <path>", "Override the code directory (default: auto-detect)").option("--dry-run", "Report what would be written without touching disk").action(async (options) => {
212367
+ program2.command("infer", { hidden: true }).description("Reverse-engineer undocumented decisions from code into inferred contracts").option("--code-dir <path>", "Override the code directory (default: auto-detect)").option("--dry-run", "Report what would be written without touching disk").action(async (options) => {
212368
+ warnDiscontinued("infer");
211659
212369
  await runInfer({ codeDir: options.codeDir, dryRun: options.dryRun });
211660
212370
  });
211661
- var driftsCmd = program2.command("drifts").description("Inspect drifts from the latest verify");
212371
+ var driftsCmd = program2.command("drifts", { hidden: true }).description("Inspect drifts from the latest verify");
211662
212372
  driftsCmd.command("list").description("List drifts from the latest verify (paginated)").option("--limit <n>", "Number of drifts to show (default: 20)", parseInt).option("--offset <n>", "Skip first N drifts", parseInt).option("--all", "Show all drifts").option(
211663
212373
  "--severity <list>",
211664
212374
  "Comma-separated severities to include (critical,high,medium,low,info)"
211665
212375
  ).action(async (options) => {
212376
+ warnDiscontinued("drifts");
211666
212377
  await runDriftsList({
211667
212378
  limit: options.all ? Infinity : options.limit ?? 20,
211668
212379
  offset: options.offset ?? 0,