truecourse 0.6.6 → 0.6.7-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/cli.mjs +1324 -288
  2. package/package.json +1 -1
  3. package/server.mjs +1343 -315
package/cli.mjs CHANGED
@@ -4882,7 +4882,7 @@ function configureLogger(config2) {
4882
4882
  async function closeLogger() {
4883
4883
  while (stack.length > 0) {
4884
4884
  const sink = stack.pop();
4885
- await new Promise((resolve9) => sink.stream.end(resolve9));
4885
+ await new Promise((resolve10) => sink.stream.end(resolve10));
4886
4886
  }
4887
4887
  }
4888
4888
  function currentSink() {
@@ -11147,6 +11147,14 @@ function parseFile(filePath, code, language) {
11147
11147
  throw new Error(`Failed to parse file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
11148
11148
  }
11149
11149
  }
11150
+ function withParsedTree(filePath, code, language, use) {
11151
+ const tree = parseFile(filePath, code, language);
11152
+ try {
11153
+ return use(tree);
11154
+ } finally {
11155
+ tree.delete();
11156
+ }
11157
+ }
11150
11158
  var _require, BUNDLED_WASM_DIR, GRAMMAR_WASM, languageCache, parserCache, initPromise, initialized;
11151
11159
  var init_parser = __esm({
11152
11160
  "packages/analyzer/dist/parser.js"() {
@@ -14006,53 +14014,15 @@ async function analyzeFile(filePath) {
14006
14014
  }
14007
14015
  try {
14008
14016
  const content = await readFile(filePath, "utf-8");
14009
- const tree = parseFile(filePath, content, language);
14010
- let functions, classes, imports, exports;
14011
- switch (language) {
14012
- case "typescript":
14013
- case "tsx":
14014
- functions = extractTypeScriptFunctions(tree, filePath, language);
14015
- classes = extractTypeScriptClasses(tree, filePath, language);
14016
- imports = extractTypeScriptImports(tree, filePath, language);
14017
- exports = extractTypeScriptExports(tree, filePath, language);
14018
- break;
14019
- case "javascript":
14020
- functions = extractJavaScriptFunctions(tree, filePath);
14021
- classes = extractJavaScriptClasses(tree, filePath);
14022
- imports = extractJavaScriptImports(tree, filePath);
14023
- exports = extractJavaScriptExports(tree, filePath);
14024
- break;
14025
- case "python":
14026
- functions = extractPythonFunctions(tree, filePath);
14027
- classes = extractPythonClasses(tree, filePath);
14028
- imports = extractPythonImports(tree, filePath);
14029
- exports = extractPythonExports(tree, filePath);
14030
- break;
14031
- default:
14032
- throw new Error(`Unsupported language: ${language}`);
14033
- }
14034
- const functionContext = buildFunctionContext(functions, classes);
14035
- const calls = extractCalls(tree, filePath, language, functionContext);
14036
- const httpCalls = extractHttpCalls(tree, filePath, language, functions, classes);
14037
- const { routes: routeRegistrations, mounts: routerMounts } = extractRouteRegistrations(tree, filePath, language);
14038
- return {
14039
- filePath,
14040
- language,
14041
- functions,
14042
- classes,
14043
- imports,
14044
- exports,
14045
- calls,
14046
- httpCalls,
14047
- ...routeRegistrations.length > 0 ? { routeRegistrations } : {},
14048
- ...routerMounts.length > 0 ? { routerMounts } : {}
14049
- };
14017
+ return withParsedTree(filePath, content, language, (tree) => buildFileAnalysis(tree, filePath, language));
14050
14018
  } catch (error) {
14051
14019
  throw new Error(`Failed to analyze file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
14052
14020
  }
14053
14021
  }
14054
14022
  function analyzeFileContent(filePath, content, language) {
14055
- const tree = parseFile(filePath, content, language);
14023
+ return withParsedTree(filePath, content, language, (tree) => buildFileAnalysis(tree, filePath, language));
14024
+ }
14025
+ function buildFileAnalysis(tree, filePath, language) {
14056
14026
  let functions, classes, imports, exports;
14057
14027
  switch (language) {
14058
14028
  case "typescript":
@@ -14068,6 +14038,12 @@ function analyzeFileContent(filePath, content, language) {
14068
14038
  imports = extractJavaScriptImports(tree, filePath);
14069
14039
  exports = extractJavaScriptExports(tree, filePath);
14070
14040
  break;
14041
+ case "python":
14042
+ functions = extractPythonFunctions(tree, filePath);
14043
+ classes = extractPythonClasses(tree, filePath);
14044
+ imports = extractPythonImports(tree, filePath);
14045
+ exports = extractPythonExports(tree, filePath);
14046
+ break;
14071
14047
  default:
14072
14048
  throw new Error(`Unsupported language: ${language}`);
14073
14049
  }
@@ -18783,11 +18759,6 @@ var init_entities = __esm({
18783
18759
  init_patterns();
18784
18760
  init_parser();
18785
18761
  TypeScriptEntityDetector = class {
18786
- // Lazy: do not call getParser() at construction time — callers may
18787
- // instantiate this before initParsers() has completed.
18788
- get parser() {
18789
- return getParser("typescript");
18790
- }
18791
18762
  shouldScanFile(filePath) {
18792
18763
  if (!/\.(ts|tsx|js|jsx)$/.test(filePath)) {
18793
18764
  return false;
@@ -18802,25 +18773,24 @@ var init_entities = __esm({
18802
18773
  return hasEntityDir || hasEntityPattern;
18803
18774
  }
18804
18775
  detectEntities(sourceCode, filePath, service) {
18805
- const tree = this.parser.parse(sourceCode);
18806
- const entities = [];
18807
- if (!tree)
18808
- return entities;
18809
- const classNodes = this.findClassDeclarations(tree.rootNode);
18810
- for (const classNode of classNodes) {
18811
- const entity = this.detectEntityFromClass(classNode, sourceCode, filePath, service);
18812
- if (entity) {
18813
- entities.push(entity);
18776
+ return withParsedTree(filePath, sourceCode, "typescript", (tree) => {
18777
+ const entities = [];
18778
+ const classNodes = this.findClassDeclarations(tree.rootNode);
18779
+ for (const classNode of classNodes) {
18780
+ const entity = this.detectEntityFromClass(classNode, sourceCode, filePath, service);
18781
+ if (entity) {
18782
+ entities.push(entity);
18783
+ }
18814
18784
  }
18815
- }
18816
- const interfaceNodes = this.findInterfaceDeclarations(tree.rootNode);
18817
- for (const interfaceNode of interfaceNodes) {
18818
- const entity = this.detectEntityFromInterface(interfaceNode, sourceCode, filePath, service);
18819
- if (entity) {
18820
- entities.push(entity);
18785
+ const interfaceNodes = this.findInterfaceDeclarations(tree.rootNode);
18786
+ for (const interfaceNode of interfaceNodes) {
18787
+ const entity = this.detectEntityFromInterface(interfaceNode, sourceCode, filePath, service);
18788
+ if (entity) {
18789
+ entities.push(entity);
18790
+ }
18821
18791
  }
18822
- }
18823
- return entities;
18792
+ return entities;
18793
+ });
18824
18794
  }
18825
18795
  findClassDeclarations(node2) {
18826
18796
  const classes = [];
@@ -19188,14 +19158,14 @@ var init_lsp_client = __esm({
19188
19158
  this.sendNotification("exit", null);
19189
19159
  } catch {
19190
19160
  }
19191
- await new Promise((resolve9) => {
19161
+ await new Promise((resolve10) => {
19192
19162
  const timeout = setTimeout(() => {
19193
19163
  this.process?.kill("SIGKILL");
19194
- resolve9();
19164
+ resolve10();
19195
19165
  }, 2e3);
19196
19166
  this.process.on("exit", () => {
19197
19167
  clearTimeout(timeout);
19198
- resolve9();
19168
+ resolve10();
19199
19169
  });
19200
19170
  });
19201
19171
  this.process = null;
@@ -19338,9 +19308,9 @@ var init_lsp_client = __esm({
19338
19308
  // Internal: JSON-RPC transport
19339
19309
  // -------------------------------------------------------------------------
19340
19310
  sendRequest(method, params) {
19341
- return new Promise((resolve9, reject) => {
19311
+ return new Promise((resolve10, reject) => {
19342
19312
  const id = ++this.requestId;
19343
- this.pendingRequests.set(id, { resolve: resolve9, reject });
19313
+ this.pendingRequests.set(id, { resolve: resolve10, reject });
19344
19314
  const msg = { jsonrpc: "2.0", id, method, params };
19345
19315
  this.process.stdin.write(encodeMessage(msg));
19346
19316
  });
@@ -19397,7 +19367,7 @@ var init_lsp_client = __esm({
19397
19367
  */
19398
19368
  waitForDiagnostics(fileCount) {
19399
19369
  const waitMs = Math.min(Math.max(fileCount * 100, 1e3), 1e4);
19400
- return new Promise((resolve9) => setTimeout(resolve9, waitMs));
19370
+ return new Promise((resolve10) => setTimeout(resolve10, waitMs));
19401
19371
  }
19402
19372
  };
19403
19373
  }
@@ -47403,6 +47373,9 @@ var init_no_self_compare = __esm({
47403
47373
  });
47404
47374
 
47405
47375
  // packages/analyzer/dist/rules/bugs/visitors/javascript/duplicate-class-members.js
47376
+ function reportDuplicate(ruleKey, child, filePath, sourceCode, name) {
47377
+ return makeViolation(ruleKey, child, filePath, "high", "Duplicate class member", `Member \`${name}\` is defined more than once \u2014 the later definition silently overwrites the earlier one.`, sourceCode, "Remove the duplicate member or rename one of them.");
47378
+ }
47406
47379
  var duplicateClassMembersVisitor;
47407
47380
  var init_duplicate_class_members = __esm({
47408
47381
  "packages/analyzer/dist/rules/bugs/visitors/javascript/duplicate-class-members.js"() {
@@ -47416,18 +47389,37 @@ var init_duplicate_class_members = __esm({
47416
47389
  visit(node2, filePath, sourceCode) {
47417
47390
  const seen = /* @__PURE__ */ new Map();
47418
47391
  for (const child of node2.namedChildren) {
47419
- let name = null;
47420
- if (child.type === "method_definition" || child.type === "public_field_definition" || child.type === "field_definition") {
47421
- const nameNode = child.childForFieldName("name");
47422
- if (nameNode)
47423
- name = nameNode.text;
47392
+ if (child.type !== "method_definition" && child.type !== "public_field_definition" && child.type !== "field_definition")
47393
+ continue;
47394
+ const nameNode = child.childForFieldName("name");
47395
+ if (!nameNode)
47396
+ continue;
47397
+ const name = nameNode.text;
47398
+ let isStatic = false;
47399
+ let accessor = null;
47400
+ for (let i = 0; i < child.childCount; i++) {
47401
+ const c2 = child.child(i);
47402
+ if (!c2)
47403
+ continue;
47404
+ if (c2.type === "static")
47405
+ isStatic = true;
47406
+ else if (c2.type === "get")
47407
+ accessor = "get";
47408
+ else if (c2.type === "set")
47409
+ accessor = "set";
47410
+ }
47411
+ const kind = accessor ?? (child.type === "method_definition" ? "method" : "field");
47412
+ const key2 = `${isStatic ? "static" : "instance"}:${name}`;
47413
+ const existing = seen.get(key2);
47414
+ if (!existing) {
47415
+ seen.set(key2, { kinds: /* @__PURE__ */ new Set([kind]) });
47416
+ continue;
47424
47417
  }
47425
- if (name) {
47426
- if (seen.has(name)) {
47427
- return makeViolation(this.ruleKey, child, filePath, "high", "Duplicate class member", `Member \`${name}\` is defined more than once \u2014 the later definition silently overwrites the earlier one.`, sourceCode, "Remove the duplicate member or rename one of them.");
47428
- }
47429
- seen.set(name, child);
47418
+ const isGetSetPair = existing.kinds.size === 1 && existing.kinds.has("get") && kind === "set" || existing.kinds.size === 1 && existing.kinds.has("set") && kind === "get";
47419
+ if (!isGetSetPair) {
47420
+ return reportDuplicate(this.ruleKey, child, filePath, sourceCode, name);
47430
47421
  }
47422
+ existing.kinds.add(kind);
47431
47423
  }
47432
47424
  return null;
47433
47425
  }
@@ -49774,7 +49766,9 @@ var init_element_overwrite = __esm({
49774
49766
  hasAwait2 = true;
49775
49767
  }
49776
49768
  const isRefCurrent = left.type === "member_expression" && indexOrProp.text === "current";
49777
- if (!wasRead && !(isRefCurrent && hasAwait2)) {
49769
+ const right = expr.childForFieldName("right");
49770
+ const readsSelfInRhs = right ? right.text.includes(left.text) : false;
49771
+ if (!wasRead && !readsSelfInRhs && !(isRefCurrent && hasAwait2)) {
49778
49772
  return makeViolation(this.ruleKey, expr, filePath, "high", "Element overwritten before read", `\`${key2}\` is assigned again before being read \u2014 the first assignment has no effect.`, sourceCode, "Remove the first assignment or use the value before overwriting it.");
49779
49773
  }
49780
49774
  }
@@ -51138,6 +51132,17 @@ var init_promise_executor_return = __esm({
51138
51132
  });
51139
51133
 
51140
51134
  // packages/analyzer/dist/rules/bugs/visitors/javascript/empty-pattern.js
51135
+ function isFunctionParameter(node2) {
51136
+ const parent = node2.parent;
51137
+ if (!parent)
51138
+ return false;
51139
+ if (parent.type === "formal_parameters")
51140
+ return true;
51141
+ if (parent.type === "required_parameter" || parent.type === "optional_parameter") {
51142
+ return parent.parent?.type === "formal_parameters";
51143
+ }
51144
+ return false;
51145
+ }
51141
51146
  var emptyPatternVisitor;
51142
51147
  var init_empty_pattern = __esm({
51143
51148
  "packages/analyzer/dist/rules/bugs/visitors/javascript/empty-pattern.js"() {
@@ -51151,6 +51156,8 @@ var init_empty_pattern = __esm({
51151
51156
  visit(node2, filePath, sourceCode) {
51152
51157
  const bindings = node2.namedChildren.filter((c2) => c2.type !== "comment");
51153
51158
  if (bindings.length === 0) {
51159
+ if (node2.type === "object_pattern" && isFunctionParameter(node2))
51160
+ return null;
51154
51161
  const kind = node2.type === "object_pattern" ? "{}" : "[]";
51155
51162
  return makeViolation(this.ruleKey, node2, filePath, "medium", "Empty destructuring pattern", `Empty destructuring pattern \`${kind}\` does not bind any variables.`, sourceCode, "Add variable bindings to the destructuring pattern or remove it entirely.");
51156
51163
  }
@@ -52702,6 +52709,15 @@ var init_contradictory_non_null_coalescing = __esm({
52702
52709
  });
52703
52710
 
52704
52711
  // packages/analyzer/dist/rules/bugs/visitors/javascript/empty-object-type.js
52712
+ function isReactPropsParameter(typeAnnotation, filePath) {
52713
+ if (!/\.(tsx|jsx)$/i.test(filePath))
52714
+ return false;
52715
+ const param = typeAnnotation.parent;
52716
+ if (param?.type !== "required_parameter" && param?.type !== "optional_parameter")
52717
+ return false;
52718
+ const pattern = param.childForFieldName("pattern");
52719
+ return pattern?.text === "props";
52720
+ }
52705
52721
  var emptyObjectTypeVisitor;
52706
52722
  var init_empty_object_type = __esm({
52707
52723
  "packages/analyzer/dist/rules/bugs/visitors/javascript/empty-object-type.js"() {
@@ -52726,7 +52742,7 @@ var init_empty_object_type = __esm({
52726
52742
  };
52727
52743
  if (node2.type === "type_annotation") {
52728
52744
  const emptyObj = checkForEmptyObject(node2);
52729
- if (emptyObj) {
52745
+ if (emptyObj && !isReactPropsParameter(node2, filePath)) {
52730
52746
  return makeViolation(this.ruleKey, emptyObj, filePath, "high", "Empty object type {}", "`{}` type matches everything except `null` and `undefined` \u2014 this is rarely intentional. Use `object` for non-primitive objects or `Record<string, unknown>` for generic objects.", sourceCode, "Replace `{}` with `object`, `Record<string, unknown>`, or a specific type.");
52731
52747
  }
52732
52748
  }
@@ -69319,6 +69335,12 @@ var init_prefer_immediate_return = __esm({
69319
69335
  const nameNode = decl.childForFieldName("name");
69320
69336
  if (nameNode?.text !== retName)
69321
69337
  return null;
69338
+ const valueNode = decl.childForFieldName("value");
69339
+ if (valueNode) {
69340
+ const lineSpan = valueNode.endPosition.row - valueNode.startPosition.row + 1;
69341
+ if (lineSpan > 10)
69342
+ return null;
69343
+ }
69322
69344
  let usageCount = 0;
69323
69345
  function countUsages2(n) {
69324
69346
  if (n.type === "identifier" && n.text === retName) {
@@ -71464,6 +71486,14 @@ function isDirectCallbackArg(fn) {
71464
71486
  p2 = p2.parent;
71465
71487
  return p2?.type === "arguments" ? p2 : null;
71466
71488
  }
71489
+ function isTrivialCallback(fn) {
71490
+ const body = fn.childForFieldName("body");
71491
+ if (!body)
71492
+ return false;
71493
+ if (body.type !== "statement_block")
71494
+ return true;
71495
+ return body.namedChildren.length === 0;
71496
+ }
71467
71497
  function findEnclosingFn(start) {
71468
71498
  let n = start;
71469
71499
  while (n) {
@@ -71485,6 +71515,8 @@ var init_deep_callback_nesting = __esm({
71485
71515
  languages: ["typescript", "tsx", "javascript"],
71486
71516
  nodeTypes: ["function_expression", "arrow_function"],
71487
71517
  visit(node2, filePath, sourceCode) {
71518
+ if (isTrivialCallback(node2))
71519
+ return null;
71488
71520
  let depth = 0;
71489
71521
  let current = node2;
71490
71522
  while (true) {
@@ -71603,6 +71635,10 @@ var init_default_parameter_position = __esm({
71603
71635
  const isOptional = param.type === "optional_parameter" || param.text.includes("?");
71604
71636
  if (isOptional)
71605
71637
  continue;
71638
+ const patternNode = param.childForFieldName("pattern") ?? param.namedChildren[0];
71639
+ const isDestructured = param.type === "object_pattern" || param.type === "array_pattern" || patternNode?.type === "object_pattern" || patternNode?.type === "array_pattern";
71640
+ if (isDestructured)
71641
+ continue;
71606
71642
  const nameNode = param.childForFieldName("pattern") ?? param.childForFieldName("name") ?? param.namedChildren[0];
71607
71643
  const name = nameNode?.text ?? "parameter";
71608
71644
  return makeViolation(this.ruleKey, param, filePath, "low", "Default parameter not last", `Required parameter \`${name}\` appears after a default parameter. Default parameters should come last.`, sourceCode, "Move default parameters to the end of the parameter list.");
@@ -72348,7 +72384,8 @@ var init_public_static_readonly = __esm({
72348
72384
  const isStatic = member.children.some((c2) => c2.type === "static");
72349
72385
  const isPublic = !member.children.some((c2) => c2.type === "accessibility_modifier" && (c2.text === "private" || c2.text === "protected"));
72350
72386
  const isReadonly = member.children.some((c2) => c2.type === "readonly");
72351
- if (isStatic && isPublic && !isReadonly) {
72387
+ const hasInitializer = member.childForFieldName("value") != null;
72388
+ if (isStatic && isPublic && !isReadonly && hasInitializer) {
72352
72389
  const nameNode = member.childForFieldName("name");
72353
72390
  const name = nameNode?.text ?? "field";
72354
72391
  return makeViolation(this.ruleKey, member, filePath, "medium", "Mutable public static field", `Public static field \`${name}\` is not \`readonly\`. Static public fields that are constants should be readonly.`, sourceCode, `Add the \`readonly\` modifier: \`public static readonly ${name}\`.`);
@@ -77799,11 +77836,18 @@ var init_confusing_void_expression = __esm({
77799
77836
  }
77800
77837
  parent = parent.parent;
77801
77838
  }
77802
- if (containingFn?.type === "arrow_function") {
77839
+ const isContainingFnVoid = () => {
77840
+ if (!containingFn)
77841
+ return false;
77803
77842
  const fnReturnType = typeQuery.getReturnType(filePath, containingFn.startPosition.row, containingFn.startPosition.column, containingFn.endPosition.row, containingFn.endPosition.column);
77804
- if (fnReturnType && /^(void|undefined|Promise<\s*(void|undefined)\s*>)$/.test(fnReturnType)) {
77843
+ return !!fnReturnType && /^(void|undefined|Promise<\s*(void|undefined)\s*>)$/.test(fnReturnType);
77844
+ };
77845
+ if (containingFn?.type === "arrow_function") {
77846
+ if (isContainingFnVoid())
77847
+ return null;
77848
+ } else if (value.type === "await_expression" && (containingFn?.type === "function_declaration" || containingFn?.type === "function_expression" || containingFn?.type === "method_definition" || containingFn?.type === "function")) {
77849
+ if (isContainingFnVoid())
77805
77850
  return null;
77806
- }
77807
77851
  }
77808
77852
  return makeViolation(this.ruleKey, node2, filePath, "low", "Returning void expression", `Returning a void expression \u2014 \`${value.text.slice(0, 40)}\` returns \`undefined\`. This is confusing because it looks like the return value matters.`, sourceCode, "Put the expression on its own line and use a bare `return` statement.");
77809
77853
  }
@@ -77989,6 +78033,32 @@ var init_unnecessary_type_parameter = __esm({
77989
78033
  });
77990
78034
 
77991
78035
  // packages/analyzer/dist/rules/code-quality/visitors/javascript/prefer-this-return-type.js
78036
+ function collectMethodReturns(body) {
78037
+ const returns = [];
78038
+ const NESTED_SCOPES = /* @__PURE__ */ new Set([
78039
+ "function_declaration",
78040
+ "function_expression",
78041
+ "function",
78042
+ "arrow_function",
78043
+ "generator_function",
78044
+ "generator_function_declaration",
78045
+ "method_definition",
78046
+ "class_declaration",
78047
+ "class"
78048
+ ]);
78049
+ const walk11 = (n) => {
78050
+ for (const child of n.namedChildren) {
78051
+ if (child.type === "return_statement") {
78052
+ returns.push(child);
78053
+ }
78054
+ if (!NESTED_SCOPES.has(child.type)) {
78055
+ walk11(child);
78056
+ }
78057
+ }
78058
+ };
78059
+ walk11(body);
78060
+ return returns;
78061
+ }
77992
78062
  function findEnclosingClass(node2) {
77993
78063
  let current = node2.parent;
77994
78064
  while (current) {
@@ -78041,6 +78111,23 @@ var init_prefer_this_return_type = __esm({
78041
78111
  if (!typeText)
78042
78112
  return null;
78043
78113
  if (typeText === className) {
78114
+ const body = node2.childForFieldName("body");
78115
+ if (!body)
78116
+ return null;
78117
+ const returns = collectMethodReturns(body);
78118
+ if (returns.length === 0)
78119
+ return null;
78120
+ let returnsThis = false;
78121
+ for (const ret of returns) {
78122
+ const arg = ret.namedChildren[0];
78123
+ if (arg && arg.type === "this") {
78124
+ returnsThis = true;
78125
+ } else {
78126
+ return null;
78127
+ }
78128
+ }
78129
+ if (!returnsThis)
78130
+ return null;
78044
78131
  return makeViolation(this.ruleKey, returnTypeNode, filePath, "low", "Method should return `this` instead of class name", `Method \`${methodName}\` returns \`${className}\` but should return \`this\` to support subclass chaining.`, sourceCode, `Change the return type from \`${className}\` to \`this\`.`);
78045
78132
  }
78046
78133
  return null;
@@ -91788,12 +91875,32 @@ var init_regex_in_loop = __esm({
91788
91875
  });
91789
91876
 
91790
91877
  // packages/analyzer/dist/rules/performance/visitors/javascript/spread-in-reduce.js
91791
- function containsSpread(node2) {
91792
- if (node2.type === "spread_element")
91793
- return true;
91878
+ function getAccumulatorName(callback) {
91879
+ if (callback.type !== "arrow_function" && callback.type !== "function_expression" && callback.type !== "function") {
91880
+ return null;
91881
+ }
91882
+ const params = callback.childForFieldName("parameters");
91883
+ if (!params)
91884
+ return null;
91885
+ const first2 = params.namedChildren.find((c2) => c2.type !== "comment");
91886
+ if (!first2)
91887
+ return null;
91888
+ if (first2.type === "identifier")
91889
+ return first2.text;
91890
+ const pattern = first2.childForFieldName("pattern");
91891
+ if (pattern && pattern.type === "identifier")
91892
+ return pattern.text;
91893
+ return null;
91894
+ }
91895
+ function spreadsIdentifier(node2, name) {
91896
+ if (node2.type === "spread_element") {
91897
+ const arg = node2.namedChildren[0];
91898
+ if (arg && arg.type === "identifier" && arg.text === name)
91899
+ return true;
91900
+ }
91794
91901
  for (let i = 0; i < node2.childCount; i++) {
91795
91902
  const child = node2.child(i);
91796
- if (child && containsSpread(child))
91903
+ if (child && spreadsIdentifier(child, name))
91797
91904
  return true;
91798
91905
  }
91799
91906
  return false;
@@ -91820,7 +91927,10 @@ var init_spread_in_reduce = __esm({
91820
91927
  const callback = args.namedChildren[0];
91821
91928
  if (!callback)
91822
91929
  return null;
91823
- if (containsSpread(callback)) {
91930
+ const accName = getAccumulatorName(callback);
91931
+ if (!accName)
91932
+ return null;
91933
+ if (spreadsIdentifier(callback, accName)) {
91824
91934
  return makeViolation(this.ruleKey, node2, filePath, "medium", "Spread operator in reduce callback", "Using spread in a reduce callback creates a new copy on every iteration, resulting in O(n^2) time complexity.", sourceCode, "Use Object.assign() or direct mutation of the accumulator instead of spread.");
91825
91935
  }
91826
91936
  return null;
@@ -91890,6 +92000,8 @@ var init_sync_fs_in_request_handler = __esm({
91890
92000
  return null;
91891
92001
  if (SEED_FILE_PATH_PATTERN2.test(filePath))
91892
92002
  return null;
92003
+ if (sourceCode.startsWith("#!"))
92004
+ return null;
91893
92005
  if (!isInsideAsyncFunctionOrHandler(node2))
91894
92006
  return null;
91895
92007
  const enclosingName = findEnclosingFunctionName(node2);
@@ -92070,6 +92182,100 @@ var init_large_bundle_import = __esm({
92070
92182
  });
92071
92183
 
92072
92184
  // packages/analyzer/dist/rules/performance/visitors/javascript/json-parse-in-loop.js
92185
+ function isDynamicPerIteration(arg, callNode) {
92186
+ switch (arg.type) {
92187
+ case "parenthesized_expression": {
92188
+ const inner = arg.namedChildren[0];
92189
+ return inner ? isDynamicPerIteration(inner, callNode) : false;
92190
+ }
92191
+ case "call_expression":
92192
+ case "subscript_expression":
92193
+ case "template_string":
92194
+ return true;
92195
+ case "ternary_expression": {
92196
+ const cons = arg.childForFieldName("consequence");
92197
+ const alt = arg.childForFieldName("alternative");
92198
+ return !!cons && isDynamicPerIteration(cons, callNode) || !!alt && isDynamicPerIteration(alt, callNode);
92199
+ }
92200
+ case "binary_expression": {
92201
+ const left = arg.childForFieldName("left");
92202
+ const right = arg.childForFieldName("right");
92203
+ return !!left && isDynamicPerIteration(left, callNode) || !!right && isDynamicPerIteration(right, callNode);
92204
+ }
92205
+ case "identifier":
92206
+ return isLoopBoundIdentifier(arg.text, callNode);
92207
+ case "member_expression": {
92208
+ const inner = arg.childForFieldName("object");
92209
+ if (!inner)
92210
+ return false;
92211
+ if (inner.type === "identifier")
92212
+ return isLoopBoundIdentifier(inner.text, callNode);
92213
+ return isDynamicPerIteration(inner, callNode);
92214
+ }
92215
+ default:
92216
+ return false;
92217
+ }
92218
+ }
92219
+ function isLoopBoundIdentifier(varName, callNode) {
92220
+ let current = callNode.parent;
92221
+ let innermostLoop = null;
92222
+ while (current) {
92223
+ if (current.type === "for_in_statement" || current.type === "for_of_statement") {
92224
+ const left = current.childForFieldName("left");
92225
+ if (left && containsIdentifierExact(left, varName))
92226
+ return true;
92227
+ if (!innermostLoop)
92228
+ innermostLoop = current;
92229
+ }
92230
+ if (current.type === "for_statement") {
92231
+ const init = current.childForFieldName("initializer");
92232
+ if (init && containsIdentifierExact(init, varName))
92233
+ return true;
92234
+ if (!innermostLoop)
92235
+ innermostLoop = current;
92236
+ }
92237
+ if (current.type === "while_statement" || current.type === "do_statement") {
92238
+ if (!innermostLoop)
92239
+ innermostLoop = current;
92240
+ }
92241
+ if (current.type === "arrow_function" || current.type === "function_expression" || current.type === "function") {
92242
+ const params = current.childForFieldName("parameters");
92243
+ if (params && containsIdentifierExact(params, varName)) {
92244
+ const callParent = current.parent?.parent;
92245
+ if (callParent?.type === "call_expression")
92246
+ return true;
92247
+ }
92248
+ }
92249
+ current = current.parent;
92250
+ }
92251
+ if (innermostLoop) {
92252
+ const body = innermostLoop.childForFieldName("body");
92253
+ if (body && containsBindingInBlock(body, varName))
92254
+ return true;
92255
+ }
92256
+ return false;
92257
+ }
92258
+ function containsBindingInBlock(node2, varName) {
92259
+ if (node2.type === "lexical_declaration" || node2.type === "variable_declaration") {
92260
+ for (const decl of node2.namedChildren) {
92261
+ if (decl.type === "variable_declarator") {
92262
+ const name = decl.childForFieldName("name");
92263
+ if (name && containsIdentifierExact(name, varName))
92264
+ return true;
92265
+ }
92266
+ }
92267
+ return false;
92268
+ }
92269
+ if (node2.type === "function_declaration" || node2.type === "arrow_function" || node2.type === "function" || node2.type === "function_expression" || node2.type === "method_definition" || node2.type === "class_declaration") {
92270
+ return false;
92271
+ }
92272
+ for (let i = 0; i < node2.childCount; i++) {
92273
+ const child = node2.child(i);
92274
+ if (child && containsBindingInBlock(child, varName))
92275
+ return true;
92276
+ }
92277
+ return false;
92278
+ }
92073
92279
  var jsonParseInLoopVisitor;
92074
92280
  var init_json_parse_in_loop = __esm({
92075
92281
  "packages/analyzer/dist/rules/performance/visitors/javascript/json-parse-in-loop.js"() {
@@ -92094,64 +92300,9 @@ var init_json_parse_in_loop = __esm({
92094
92300
  if (!isInsideLoop(node2))
92095
92301
  return null;
92096
92302
  const args = node2.childForFieldName("arguments");
92097
- if (args) {
92098
- const firstArg = args.namedChildren[0];
92099
- if (firstArg) {
92100
- if (firstArg.type === "call_expression")
92101
- return null;
92102
- if (firstArg.type === "subscript_expression")
92103
- return null;
92104
- if (firstArg.type === "template_string")
92105
- return null;
92106
- if (firstArg.type === "identifier") {
92107
- const varName = firstArg.text;
92108
- let current = node2.parent;
92109
- while (current) {
92110
- if (current.type === "for_in_statement" || current.type === "for_of_statement") {
92111
- const left = current.childForFieldName("left");
92112
- if (left && containsIdentifierExact(left, varName))
92113
- return null;
92114
- }
92115
- if (current.type === "for_statement") {
92116
- const init = current.childForFieldName("initializer");
92117
- if (init && containsIdentifierExact(init, varName))
92118
- return null;
92119
- }
92120
- if (current.type === "arrow_function" || current.type === "function_expression" || current.type === "function") {
92121
- const params = current.childForFieldName("parameters");
92122
- if (params && containsIdentifierExact(params, varName)) {
92123
- const callParent = current.parent?.parent;
92124
- if (callParent?.type === "call_expression")
92125
- return null;
92126
- }
92127
- }
92128
- current = current.parent;
92129
- }
92130
- }
92131
- if (firstArg.type === "member_expression") {
92132
- const innerObj = firstArg.childForFieldName("object");
92133
- if (innerObj?.type === "identifier") {
92134
- let current = node2.parent;
92135
- while (current) {
92136
- if (current.type === "for_in_statement" || current.type === "for_of_statement") {
92137
- const left = current.childForFieldName("left");
92138
- if (left && containsIdentifierExact(left, innerObj.text))
92139
- return null;
92140
- }
92141
- if (current.type === "arrow_function" || current.type === "function_expression" || current.type === "function") {
92142
- const params = current.childForFieldName("parameters");
92143
- if (params && containsIdentifierExact(params, innerObj.text)) {
92144
- const callParent = current.parent?.parent;
92145
- if (callParent?.type === "call_expression")
92146
- return null;
92147
- }
92148
- }
92149
- current = current.parent;
92150
- }
92151
- }
92152
- }
92153
- }
92154
- }
92303
+ const firstArg = args?.namedChildren[0];
92304
+ if (firstArg && isDynamicPerIteration(firstArg, node2))
92305
+ return null;
92155
92306
  return makeViolation(this.ruleKey, node2, filePath, "medium", `JSON.${prop.text}() inside loop`, `JSON.${prop.text}() is expensive and calling it inside a loop degrades performance. Move it outside the loop if possible.`, sourceCode, `Cache the result of JSON.${prop.text}() outside the loop.`);
92156
92307
  }
92157
92308
  };
@@ -92484,6 +92635,19 @@ var init_missing_usememo_expensive = __esm({
92484
92635
  return null;
92485
92636
  }
92486
92637
  }
92638
+ if (prop.text === "reduce") {
92639
+ const args = node2.childForFieldName("arguments");
92640
+ const callback = args?.namedChild(0);
92641
+ if (callback?.type === "arrow_function" || callback?.type === "function_expression" || callback?.type === "function") {
92642
+ const body = callback.childForFieldName("body");
92643
+ let inspect = body;
92644
+ if (inspect?.type === "parenthesized_expression") {
92645
+ inspect = inspect.namedChildren[0] ?? inspect;
92646
+ }
92647
+ if (inspect?.type === "binary_expression")
92648
+ return null;
92649
+ }
92650
+ }
92487
92651
  const enclosingFn = findEnclosingFunctionNode(node2);
92488
92652
  if (!enclosingFn)
92489
92653
  return null;
@@ -93479,6 +93643,37 @@ var init_python9 = __esm({
93479
93643
  });
93480
93644
 
93481
93645
  // packages/analyzer/dist/rules/reliability/visitors/javascript/catch-without-error-type.js
93646
+ function narrowsViaTypeGuard(node2, paramName2) {
93647
+ if (node2.type === "call_expression") {
93648
+ const fn = node2.childForFieldName("function");
93649
+ let calleeName = null;
93650
+ if (fn?.type === "identifier")
93651
+ calleeName = fn.text;
93652
+ else if (fn?.type === "member_expression")
93653
+ calleeName = fn.childForFieldName("property")?.text ?? null;
93654
+ if (calleeName && TYPE_GUARD_NAME.test(calleeName)) {
93655
+ const args = node2.childForFieldName("arguments");
93656
+ if (args && referencesBinding(args, paramName2))
93657
+ return true;
93658
+ }
93659
+ }
93660
+ for (let i = 0; i < node2.childCount; i++) {
93661
+ const ch = node2.child(i);
93662
+ if (ch && narrowsViaTypeGuard(ch, paramName2))
93663
+ return true;
93664
+ }
93665
+ return false;
93666
+ }
93667
+ function referencesBinding(node2, name) {
93668
+ if (node2.type === "identifier" && node2.text === name)
93669
+ return true;
93670
+ for (let i = 0; i < node2.childCount; i++) {
93671
+ const ch = node2.child(i);
93672
+ if (ch && referencesBinding(ch, name))
93673
+ return true;
93674
+ }
93675
+ return false;
93676
+ }
93482
93677
  function hasBranchingConstruct(node2) {
93483
93678
  if (node2.type === "if_statement" || node2.type === "switch_statement" || node2.type === "ternary_expression")
93484
93679
  return true;
@@ -93491,7 +93686,7 @@ function hasBranchingConstruct(node2) {
93491
93686
  }
93492
93687
  return false;
93493
93688
  }
93494
- var catchWithoutErrorTypeVisitor;
93689
+ var catchWithoutErrorTypeVisitor, TYPE_GUARD_NAME;
93495
93690
  var init_catch_without_error_type = __esm({
93496
93691
  "packages/analyzer/dist/rules/reliability/visitors/javascript/catch-without-error-type.js"() {
93497
93692
  "use strict";
@@ -93519,9 +93714,13 @@ var init_catch_without_error_type = __esm({
93519
93714
  return null;
93520
93715
  if (!hasBranchingConstruct(body))
93521
93716
  return null;
93717
+ const paramName2 = param.type === "identifier" ? param.text : null;
93718
+ if (paramName2 && narrowsViaTypeGuard(body, paramName2))
93719
+ return null;
93522
93720
  return makeViolation(this.ruleKey, node2, filePath, "medium", "Catch without error type discrimination", "Catch block does not check or narrow the error type. Different error types may need different handling.", sourceCode, "Use instanceof checks or type guards in the catch block to handle specific error types.");
93523
93721
  }
93524
93722
  };
93723
+ TYPE_GUARD_NAME = /^(is|has|assert)[A-Z]/;
93525
93724
  }
93526
93725
  });
93527
93726
 
@@ -98763,7 +98962,8 @@ __export(dist_exports2, {
98763
98962
  shouldExtractEntities: () => shouldExtractEntities,
98764
98963
  toLayerDetectionResults: () => toLayerDetectionResults,
98765
98964
  traceFlows: () => traceFlows,
98766
- walkAstWithVisitors: () => walkAstWithVisitors
98965
+ walkAstWithVisitors: () => walkAstWithVisitors,
98966
+ withParsedTree: () => withParsedTree
98767
98967
  });
98768
98968
  async function analyzeRepository(rootPath) {
98769
98969
  await initParsers();
@@ -104140,6 +104340,12 @@ function loadTcIgnore(startDir) {
104140
104340
  if (rel === "" || rel.startsWith(".."))
104141
104341
  return false;
104142
104342
  return ig.ignores(rel);
104343
+ },
104344
+ reincludes(absPath) {
104345
+ const rel = path12.relative(root, path12.resolve(absPath)).split(path12.sep).join("/");
104346
+ if (rel === "" || rel.startsWith(".."))
104347
+ return false;
104348
+ return ig.test(rel).unignored;
104143
104349
  }
104144
104350
  };
104145
104351
  }
@@ -104377,12 +104583,12 @@ var require_isexe = __commonJS({
104377
104583
  if (typeof Promise !== "function") {
104378
104584
  throw new TypeError("callback not provided");
104379
104585
  }
104380
- return new Promise(function(resolve9, reject) {
104586
+ return new Promise(function(resolve10, reject) {
104381
104587
  isexe(path61, options || {}, function(er, is) {
104382
104588
  if (er) {
104383
104589
  reject(er);
104384
104590
  } else {
104385
- resolve9(is);
104591
+ resolve10(is);
104386
104592
  }
104387
104593
  });
104388
104594
  });
@@ -104448,27 +104654,27 @@ var require_which = __commonJS({
104448
104654
  opt = {};
104449
104655
  const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
104450
104656
  const found = [];
104451
- const step = (i) => new Promise((resolve9, reject) => {
104657
+ const step = (i) => new Promise((resolve10, reject) => {
104452
104658
  if (i === pathEnv.length)
104453
- return opt.all && found.length ? resolve9(found) : reject(getNotFoundError(cmd));
104659
+ return opt.all && found.length ? resolve10(found) : reject(getNotFoundError(cmd));
104454
104660
  const ppRaw = pathEnv[i];
104455
104661
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
104456
104662
  const pCmd = path61.join(pathPart, cmd);
104457
104663
  const p2 = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
104458
- resolve9(subStep(p2, i, 0));
104664
+ resolve10(subStep(p2, i, 0));
104459
104665
  });
104460
- const subStep = (p2, i, ii) => new Promise((resolve9, reject) => {
104666
+ const subStep = (p2, i, ii) => new Promise((resolve10, reject) => {
104461
104667
  if (ii === pathExt.length)
104462
- return resolve9(step(i + 1));
104668
+ return resolve10(step(i + 1));
104463
104669
  const ext2 = pathExt[ii];
104464
104670
  isexe(p2 + ext2, { pathExt: pathExtExe }, (er, is) => {
104465
104671
  if (!er && is) {
104466
104672
  if (opt.all)
104467
104673
  found.push(p2 + ext2);
104468
104674
  else
104469
- return resolve9(p2 + ext2);
104675
+ return resolve10(p2 + ext2);
104470
104676
  }
104471
- return resolve9(subStep(p2, i, ii + 1));
104677
+ return resolve10(subStep(p2, i, ii + 1));
104472
104678
  });
104473
104679
  });
104474
104680
  return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
@@ -104873,27 +105079,27 @@ function pLimit(concurrency) {
104873
105079
  activeCount--;
104874
105080
  resumeNext();
104875
105081
  };
104876
- const run = async (function_, resolve9, arguments_) => {
105082
+ const run = async (function_, resolve10, arguments_) => {
104877
105083
  const result = (async () => function_(...arguments_))();
104878
- resolve9(result);
105084
+ resolve10(result);
104879
105085
  try {
104880
105086
  await result;
104881
105087
  } catch {
104882
105088
  }
104883
105089
  next();
104884
105090
  };
104885
- const enqueue = (function_, resolve9, reject, arguments_) => {
105091
+ const enqueue = (function_, resolve10, reject, arguments_) => {
104886
105092
  const queueItem = { reject };
104887
105093
  new Promise((internalResolve) => {
104888
105094
  queueItem.run = internalResolve;
104889
105095
  queue.enqueue(queueItem);
104890
- }).then(run.bind(void 0, function_, resolve9, arguments_));
105096
+ }).then(run.bind(void 0, function_, resolve10, arguments_));
104891
105097
  if (activeCount < concurrency) {
104892
105098
  resumeNext();
104893
105099
  }
104894
105100
  };
104895
- const generator = (function_, ...arguments_) => new Promise((resolve9, reject) => {
104896
- enqueue(function_, resolve9, reject, arguments_);
105101
+ const generator = (function_, ...arguments_) => new Promise((resolve10, reject) => {
105102
+ enqueue(function_, resolve10, reject, arguments_);
104897
105103
  });
104898
105104
  Object.defineProperties(generator, {
104899
105105
  activeCount: {
@@ -107746,7 +107952,7 @@ var init_cli_provider = __esm({
107746
107952
  jsonSchemaStr,
107747
107953
  ...opts?.extraArgs ?? []
107748
107954
  ];
107749
- return new Promise((resolve9, reject) => {
107955
+ return new Promise((resolve10, reject) => {
107750
107956
  const child = (0, import_cross_spawn.default)(this.binaryName, args, {
107751
107957
  env: this.getCleanEnv(),
107752
107958
  stdio: ["pipe", "pipe", "pipe"],
@@ -107794,7 +108000,7 @@ var init_cli_provider = __esm({
107794
108000
  reject(new Error(`[CLI] ${this.binaryName} exited with code ${code}: ${detail}`));
107795
108001
  return;
107796
108002
  }
107797
- resolve9(stdout);
108003
+ resolve10(stdout);
107798
108004
  });
107799
108005
  child.on("error", (err) => {
107800
108006
  clearTimeout(timer);
@@ -109303,12 +109509,12 @@ async function runViolationPipeline(input) {
109303
109509
  const fileContents = /* @__PURE__ */ new Map();
109304
109510
  const totalToScan = filesToScan.length;
109305
109511
  let scanned = 0;
109306
- for (const { filePath, resolve: resolve9 } of filesToScan) {
109512
+ for (const { filePath, resolve: resolve10 } of filesToScan) {
109307
109513
  try {
109308
109514
  const lang = detectLanguage(filePath);
109309
109515
  if (!lang)
109310
109516
  continue;
109311
- const absPath = resolve9 ? path14.resolve(repoPath, filePath) : path14.isAbsolute(filePath) ? filePath : path14.join(repoPath, filePath);
109517
+ const absPath = resolve10 ? path14.resolve(repoPath, filePath) : path14.isAbsolute(filePath) ? filePath : path14.join(repoPath, filePath);
109312
109518
  if (!fs12.existsSync(absPath))
109313
109519
  continue;
109314
109520
  const content = fs12.readFileSync(absPath, "utf-8");
@@ -109413,18 +109619,17 @@ async function runViolationPipeline(input) {
109413
109619
  const DETAIL_UPDATE_MS = 100;
109414
109620
  let lastYieldMs = Date.now();
109415
109621
  let lastDetailMs = lastYieldMs;
109416
- for (const { filePath, resolve: resolve9 } of filesToScan) {
109622
+ for (const { filePath, resolve: resolve10 } of filesToScan) {
109417
109623
  try {
109418
109624
  const lang = detectLanguage(filePath);
109419
109625
  if (!lang)
109420
109626
  continue;
109421
- const absPath = resolve9 ? path14.resolve(repoPath, filePath) : path14.isAbsolute(filePath) ? filePath : path14.join(repoPath, filePath);
109627
+ const absPath = resolve10 ? path14.resolve(repoPath, filePath) : path14.isAbsolute(filePath) ? filePath : path14.join(repoPath, filePath);
109422
109628
  const key2 = changedFileSet ? absPath : filePath;
109423
109629
  const fc = fileContents.get(key2);
109424
109630
  if (!fc)
109425
109631
  continue;
109426
- const tree = parseFile(changedFileSet ? filePath : filePath, fc.content, lang);
109427
- const codeRuleViolations = checkCodeRules(tree, changedFileSet ? absPath : filePath, fc.content, enabledCodeRules, lang, typeQuery, schemaIndex);
109632
+ const codeRuleViolations = withParsedTree(filePath, fc.content, lang, (tree) => checkCodeRules(tree, changedFileSet ? absPath : filePath, fc.content, enabledCodeRules, lang, typeQuery, schemaIndex));
109428
109633
  allCodeViolations.push(...codeRuleViolations);
109429
109634
  } catch {
109430
109635
  }
@@ -123260,10 +123465,10 @@ var require_axios = __commonJS({
123260
123465
  this.__CANCEL__ = true;
123261
123466
  }
123262
123467
  };
123263
- function settle(resolve9, reject, response) {
123468
+ function settle(resolve10, reject, response) {
123264
123469
  const validateStatus = response.config.validateStatus;
123265
123470
  if (!response.status || !validateStatus || validateStatus(response.status)) {
123266
- resolve9(response);
123471
+ resolve10(response);
123267
123472
  } else {
123268
123473
  reject(new AxiosError("Request failed with status code " + response.status, [AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4], response.config, response.request, response));
123269
123474
  }
@@ -123906,7 +124111,7 @@ var require_axios = __commonJS({
123906
124111
  }
123907
124112
  var isHttpAdapterSupported = typeof process !== "undefined" && utils$1.kindOf(process) === "process";
123908
124113
  var wrapAsync = (asyncExecutor) => {
123909
- return new Promise((resolve9, reject) => {
124114
+ return new Promise((resolve10, reject) => {
123910
124115
  let onDone;
123911
124116
  let isDone;
123912
124117
  const done = (value, isRejected) => {
@@ -123916,7 +124121,7 @@ var require_axios = __commonJS({
123916
124121
  };
123917
124122
  const _resolve = (value) => {
123918
124123
  done(value);
123919
- resolve9(value);
124124
+ resolve10(value);
123920
124125
  };
123921
124126
  const _reject = (reason) => {
123922
124127
  done(reason, true);
@@ -123977,7 +124182,7 @@ var require_axios = __commonJS({
123977
124182
  }
123978
124183
  };
123979
124184
  var httpAdapter = isHttpAdapterSupported && function httpAdapter2(config2) {
123980
- return wrapAsync(async function dispatchHttpRequest(resolve9, reject, onDone) {
124185
+ return wrapAsync(async function dispatchHttpRequest(resolve10, reject, onDone) {
123981
124186
  let {
123982
124187
  data,
123983
124188
  lookup,
@@ -124069,7 +124274,7 @@ var require_axios = __commonJS({
124069
124274
  }
124070
124275
  let convertedData;
124071
124276
  if (method !== "GET") {
124072
- return settle(resolve9, reject, {
124277
+ return settle(resolve10, reject, {
124073
124278
  status: 405,
124074
124279
  statusText: "method not allowed",
124075
124280
  headers: {},
@@ -124091,7 +124296,7 @@ var require_axios = __commonJS({
124091
124296
  } else if (responseType === "stream") {
124092
124297
  convertedData = stream.Readable.from(convertedData);
124093
124298
  }
124094
- return settle(resolve9, reject, {
124299
+ return settle(resolve10, reject, {
124095
124300
  data: convertedData,
124096
124301
  status: 200,
124097
124302
  statusText: "OK",
@@ -124286,7 +124491,7 @@ var require_axios = __commonJS({
124286
124491
  };
124287
124492
  if (responseType === "stream") {
124288
124493
  response.data = responseStream;
124289
- settle(resolve9, reject, response);
124494
+ settle(resolve10, reject, response);
124290
124495
  } else {
124291
124496
  const responseBuffer = [];
124292
124497
  let totalResponseBytes = 0;
@@ -124324,7 +124529,7 @@ var require_axios = __commonJS({
124324
124529
  } catch (err) {
124325
124530
  return reject(AxiosError.from(err, null, config2, response.request, response));
124326
124531
  }
124327
- settle(resolve9, reject, response);
124532
+ settle(resolve10, reject, response);
124328
124533
  });
124329
124534
  }
124330
124535
  abortEmitter.once("abort", (err) => {
@@ -124562,7 +124767,7 @@ var require_axios = __commonJS({
124562
124767
  };
124563
124768
  var isXHRAdapterSupported = typeof XMLHttpRequest !== "undefined";
124564
124769
  var xhrAdapter = isXHRAdapterSupported && function(config2) {
124565
- return new Promise(function dispatchXhrRequest(resolve9, reject) {
124770
+ return new Promise(function dispatchXhrRequest(resolve10, reject) {
124566
124771
  const _config = resolveConfig(config2);
124567
124772
  let requestData = _config.data;
124568
124773
  const requestHeaders = AxiosHeaders.from(_config.headers).normalize();
@@ -124598,7 +124803,7 @@ var require_axios = __commonJS({
124598
124803
  request
124599
124804
  };
124600
124805
  settle(function _resolve(value) {
124601
- resolve9(value);
124806
+ resolve10(value);
124602
124807
  done();
124603
124808
  }, function _reject(err) {
124604
124809
  reject(err);
@@ -124969,8 +125174,8 @@ var require_axios = __commonJS({
124969
125174
  responseType = responseType || "text";
124970
125175
  let responseData = await resolvers[utils$1.findKey(resolvers, responseType) || "text"](response, config2);
124971
125176
  !isStreamResponse && unsubscribe && unsubscribe();
124972
- return await new Promise((resolve9, reject) => {
124973
- settle(resolve9, reject, {
125177
+ return await new Promise((resolve10, reject) => {
125178
+ settle(resolve10, reject, {
124974
125179
  data: responseData,
124975
125180
  headers: AxiosHeaders.from(response.headers),
124976
125181
  status: response.status,
@@ -125339,8 +125544,8 @@ var require_axios = __commonJS({
125339
125544
  throw new TypeError("executor must be a function.");
125340
125545
  }
125341
125546
  let resolvePromise;
125342
- this.promise = new Promise(function promiseExecutor(resolve9) {
125343
- resolvePromise = resolve9;
125547
+ this.promise = new Promise(function promiseExecutor(resolve10) {
125548
+ resolvePromise = resolve10;
125344
125549
  });
125345
125550
  const token = this;
125346
125551
  this.promise.then((cancel) => {
@@ -125353,9 +125558,9 @@ var require_axios = __commonJS({
125353
125558
  });
125354
125559
  this.promise.then = (onfulfilled) => {
125355
125560
  let _resolve;
125356
- const promise = new Promise((resolve9) => {
125357
- token.subscribe(resolve9);
125358
- _resolve = resolve9;
125561
+ const promise = new Promise((resolve10) => {
125562
+ token.subscribe(resolve10);
125563
+ _resolve = resolve10;
125359
125564
  }).then(onfulfilled);
125360
125565
  promise.cancel = function reject() {
125361
125566
  token.unsubscribe(_resolve);
@@ -125969,14 +126174,14 @@ async function addSourceContext(frames) {
125969
126174
  return frames;
125970
126175
  }
125971
126176
  function getContextLinesFromFile(path61, ranges, output) {
125972
- return new Promise((resolve9) => {
126177
+ return new Promise((resolve10) => {
125973
126178
  const stream = createReadStream(path61);
125974
126179
  const lineReaded = createInterface2({
125975
126180
  input: stream
125976
126181
  });
125977
126182
  function destroyStreamAndResolve() {
125978
126183
  stream.destroy();
125979
- resolve9();
126184
+ resolve10();
125980
126185
  }
125981
126186
  let lineNumber = 0;
125982
126187
  let currentRangeIndex = 0;
@@ -129082,15 +129287,15 @@ var init_node = __esm({
129082
129287
  if (this.featureFlagsPoller === void 0) {
129083
129288
  return false;
129084
129289
  }
129085
- return new Promise((resolve9) => {
129290
+ return new Promise((resolve10) => {
129086
129291
  const timeout = setTimeout(() => {
129087
129292
  cleanup();
129088
- resolve9(false);
129293
+ resolve10(false);
129089
129294
  }, timeoutMs);
129090
129295
  const cleanup = this._events.on("localEvaluationFlagsLoaded", (count) => {
129091
129296
  clearTimeout(timeout);
129092
129297
  cleanup();
129093
- resolve9(count > 0);
129298
+ resolve10(count > 0);
129094
129299
  });
129095
129300
  });
129096
129301
  }
@@ -129415,7 +129620,7 @@ function readToolVersion() {
129415
129620
  if (cachedVersion)
129416
129621
  return cachedVersion;
129417
129622
  if (true) {
129418
- cachedVersion = "0.6.6";
129623
+ cachedVersion = "0.6.7-next.1";
129419
129624
  return cachedVersion;
129420
129625
  }
129421
129626
  try {
@@ -132744,6 +132949,77 @@ var init_operation2 = __esm({
132744
132949
  });
132745
132950
 
132746
132951
  // packages/contract-verifier/dist/extractor/operation-fastapi.js
132952
+ function extractStarletteRoutesFromFile(filePath, source, tree) {
132953
+ const out = [];
132954
+ walk(tree.rootNode, (node2) => {
132955
+ if (node2.type !== "call")
132956
+ return;
132957
+ const fn = node2.childForFieldName("function");
132958
+ if (fn?.type !== "identifier" || source.slice(fn.startIndex, fn.endIndex) !== "Route")
132959
+ return;
132960
+ const args = node2.childForFieldName("arguments");
132961
+ if (!args)
132962
+ return;
132963
+ const first2 = args.namedChild(0);
132964
+ if (first2?.type !== "string")
132965
+ return;
132966
+ const rawPath = pyStr(first2, source);
132967
+ if (!rawPath.startsWith("/"))
132968
+ return;
132969
+ const second = args.namedChild(1);
132970
+ if (!second || second.type === "keyword_argument")
132971
+ return;
132972
+ const methods = readStarletteMethods(args, source);
132973
+ const identities = starletteIdentitiesForPath(rawPath);
132974
+ const line = node2.startPosition.row + 1;
132975
+ for (const method of methods) {
132976
+ for (const path61 of identities) {
132977
+ out.push({
132978
+ identity: `${method} ${path61}`,
132979
+ contract: {
132980
+ protocol: "http",
132981
+ method,
132982
+ path: path61,
132983
+ responses: [],
132984
+ tags: []
132985
+ },
132986
+ filePath,
132987
+ declarationLine: line,
132988
+ observed: { queryParams: [], numericClamps: [], hasClampCall: false }
132989
+ });
132990
+ }
132991
+ }
132992
+ });
132993
+ return out;
132994
+ }
132995
+ function readStarletteMethods(args, source) {
132996
+ for (let i = 0; i < args.namedChildCount; i++) {
132997
+ const a = args.namedChild(i);
132998
+ if (a?.type !== "keyword_argument")
132999
+ continue;
133000
+ const name = a.childForFieldName("name");
133001
+ if (!name || source.slice(name.startIndex, name.endIndex) !== "methods")
133002
+ continue;
133003
+ const value = a.childForFieldName("value");
133004
+ if (value?.type !== "list")
133005
+ return ["GET"];
133006
+ const methods = [];
133007
+ for (let j2 = 0; j2 < value.namedChildCount; j2++) {
133008
+ const el = value.namedChild(j2);
133009
+ if (el?.type === "string")
133010
+ methods.push(pyStr(el, source).toUpperCase());
133011
+ }
133012
+ return methods.length > 0 ? methods : ["GET"];
133013
+ }
133014
+ return ["GET"];
133015
+ }
133016
+ function starletteIdentitiesForPath(rawPath) {
133017
+ const catchAll = rawPath.match(/^(\/.*\/)\{\w+:path\}$/);
133018
+ if (catchAll) {
133019
+ return [catchAll[1].replace(/\{(\w+):[^}]+\}/g, "{$1}")];
133020
+ }
133021
+ return [rawPath.replace(/\{(\w+):[^}]+\}/g, "{$1}")];
133022
+ }
132747
133023
  function extractFastApiOperationsFromFile(filePath, source, tree) {
132748
133024
  const routers = collectRouters(tree.rootNode, source);
132749
133025
  const stringVars = collectStringVars(tree.rootNode, source);
@@ -136348,9 +136624,85 @@ var init_ts_enums = __esm({
136348
136624
  }
136349
136625
  });
136350
136626
 
136627
+ // packages/contract-verifier/dist/extractor/py-string-resolver.js
136628
+ function collectStringConstantTable(rootNode, source) {
136629
+ const table = /* @__PURE__ */ new Map();
136630
+ for (let i = 0; i < rootNode.namedChildCount; i++) {
136631
+ const stmt = rootNode.namedChild(i);
136632
+ if (stmt?.type !== "expression_statement")
136633
+ continue;
136634
+ const assign = stmt.namedChild(0);
136635
+ if (assign?.type !== "assignment")
136636
+ continue;
136637
+ const left = assign.childForFieldName("left");
136638
+ if (left?.type !== "identifier")
136639
+ continue;
136640
+ const right = assign.childForFieldName("right");
136641
+ if (right?.type !== "string")
136642
+ continue;
136643
+ const name = source.slice(left.startIndex, left.endIndex);
136644
+ if (table.has(name))
136645
+ continue;
136646
+ table.set(name, right);
136647
+ }
136648
+ return table;
136649
+ }
136650
+ function stringValue(node2, source, table) {
136651
+ return resolve9(node2, source, table, 0);
136652
+ }
136653
+ function resolve9(node2, source, table, depth) {
136654
+ if (node2.type !== "string")
136655
+ return null;
136656
+ if (depth > MAX_DEPTH3)
136657
+ return null;
136658
+ let content = "";
136659
+ let sawContent = false;
136660
+ for (let i = 0; i < node2.namedChildCount; i++) {
136661
+ const c2 = node2.namedChild(i);
136662
+ if (!c2)
136663
+ continue;
136664
+ if (c2.type === "string_content") {
136665
+ content += source.slice(c2.startIndex, c2.endIndex);
136666
+ sawContent = true;
136667
+ continue;
136668
+ }
136669
+ if (c2.type === "interpolation") {
136670
+ if (c2.namedChildren.some((g) => g?.type === "format_specifier"))
136671
+ return null;
136672
+ const expr = c2.namedChild(0);
136673
+ if (expr?.type !== "identifier" || !table)
136674
+ return null;
136675
+ const referent = table.get(source.slice(expr.startIndex, expr.endIndex));
136676
+ if (!referent)
136677
+ return null;
136678
+ const resolved = resolve9(referent, source, table, depth + 1);
136679
+ if (resolved === null)
136680
+ return null;
136681
+ content += resolved;
136682
+ sawContent = true;
136683
+ continue;
136684
+ }
136685
+ if (c2.type === "format_specifier")
136686
+ return null;
136687
+ }
136688
+ if (sawContent)
136689
+ return content;
136690
+ const raw = source.slice(node2.startIndex, node2.endIndex);
136691
+ const m = raw.match(/^[a-zA-Z]*('''|"""|'|")([\s\S]*)\1$/);
136692
+ return m ? m[2] : null;
136693
+ }
136694
+ var MAX_DEPTH3;
136695
+ var init_py_string_resolver = __esm({
136696
+ "packages/contract-verifier/dist/extractor/py-string-resolver.js"() {
136697
+ "use strict";
136698
+ MAX_DEPTH3 = 4;
136699
+ }
136700
+ });
136701
+
136351
136702
  // packages/contract-verifier/dist/extractor/enum/py-enums.js
136352
136703
  function extractPyEnumsFromFile(filePath, source, tree) {
136353
136704
  const out = [];
136705
+ const stringTable = collectStringConstantTable(tree.rootNode, source);
136354
136706
  walk7(tree.rootNode, (node2) => {
136355
136707
  if (node2.type === "class_definition") {
136356
136708
  const decl = extractEnumClass(node2, filePath, source);
@@ -136366,6 +136718,302 @@ function extractPyEnumsFromFile(filePath, source, tree) {
136366
136718
  }
136367
136719
  return true;
136368
136720
  });
136721
+ out.push(...synthesizeInstanceRegistryEnum(tree.rootNode, filePath, source));
136722
+ out.push(...synthesizeDiscriminatedUnionEnum(tree.rootNode, filePath, source));
136723
+ out.push(...synthesizeConstantClusterEnums(tree.rootNode, filePath, source, stringTable));
136724
+ return out;
136725
+ }
136726
+ function synthesizeDiscriminatedUnionEnum(root, filePath, source) {
136727
+ const discriminatorOf = /* @__PURE__ */ new Map();
136728
+ walk7(root, (node2) => {
136729
+ if (node2.type !== "class_definition")
136730
+ return true;
136731
+ const className = textOfField(node2, "name", source);
136732
+ const body = node2.childForFieldName("body");
136733
+ if (!className || !body)
136734
+ return true;
136735
+ for (let i = 0; i < body.namedChildCount; i++) {
136736
+ const stmt = body.namedChild(i);
136737
+ if (stmt?.type !== "expression_statement")
136738
+ continue;
136739
+ const assign = stmt.namedChild(0);
136740
+ if (assign?.type !== "assignment")
136741
+ continue;
136742
+ const left = assign.childForFieldName("left");
136743
+ const typeAnn = assign.childForFieldName("type");
136744
+ if (left?.type !== "identifier" || !typeAnn)
136745
+ continue;
136746
+ if (source.slice(left.startIndex, left.endIndex) !== "type")
136747
+ continue;
136748
+ const lit = literalStringOfAnnotation(typeAnn, source);
136749
+ if (lit !== null)
136750
+ discriminatorOf.set(className, lit);
136751
+ }
136752
+ return true;
136753
+ });
136754
+ if (discriminatorOf.size === 0)
136755
+ return [];
136756
+ const out = [];
136757
+ for (let i = 0; i < root.namedChildCount; i++) {
136758
+ const stmt = root.namedChild(i);
136759
+ if (stmt?.type !== "expression_statement")
136760
+ continue;
136761
+ const assign = stmt.namedChild(0);
136762
+ if (assign?.type !== "assignment")
136763
+ continue;
136764
+ const left = assign.childForFieldName("left");
136765
+ if (left?.type !== "identifier")
136766
+ continue;
136767
+ const aliasName = source.slice(left.startIndex, left.endIndex);
136768
+ const right = assign.childForFieldName("right");
136769
+ if (!right)
136770
+ continue;
136771
+ const memberClasses = unionMemberIdentifiers(right, source);
136772
+ if (memberClasses.length === 0)
136773
+ continue;
136774
+ const values = memberClasses.map((m) => discriminatorOf.get(m)).filter((v) => v !== void 0);
136775
+ if (values.length < 3)
136776
+ continue;
136777
+ out.push(mkEnum2(aliasName, values, "py-discriminated-union", root, filePath));
136778
+ }
136779
+ return out;
136780
+ }
136781
+ function literalStringOfAnnotation(ann, source) {
136782
+ let node2 = ann;
136783
+ if (node2.type === "type")
136784
+ node2 = node2.namedChild(0);
136785
+ if (!node2 || node2.type !== "generic_type" && node2.type !== "subscript")
136786
+ return null;
136787
+ const base = node2.namedChild(0);
136788
+ if (!base || !source.slice(base.startIndex, base.endIndex).endsWith("Literal"))
136789
+ return null;
136790
+ const strings = collectStringsDeep(node2, source);
136791
+ return strings.length === 1 ? strings[0] : null;
136792
+ }
136793
+ function collectStringsDeep(node2, source) {
136794
+ const out = [];
136795
+ const visit = (n) => {
136796
+ if (n.type === "string") {
136797
+ const v = stringValue(n, source);
136798
+ if (v !== null)
136799
+ out.push(v);
136800
+ return;
136801
+ }
136802
+ for (let i = 0; i < n.namedChildCount; i++) {
136803
+ const c2 = n.namedChild(i);
136804
+ if (c2)
136805
+ visit(c2);
136806
+ }
136807
+ };
136808
+ visit(node2);
136809
+ return out;
136810
+ }
136811
+ function unionMemberIdentifiers(node2, source) {
136812
+ if (node2.type === "subscript") {
136813
+ const value = node2.childForFieldName("value");
136814
+ if (value && source.slice(value.startIndex, value.endIndex).endsWith("Union")) {
136815
+ const out = [];
136816
+ for (let i = 0; i < node2.namedChildCount; i++) {
136817
+ const c2 = node2.namedChild(i);
136818
+ if (c2 && c2 !== value)
136819
+ collectTypeIdentifiers(c2, out, source);
136820
+ }
136821
+ return out;
136822
+ }
136823
+ return [];
136824
+ }
136825
+ if (node2.type === "binary_operator") {
136826
+ const text = source.slice(node2.startIndex, node2.endIndex);
136827
+ if (text.includes("|")) {
136828
+ const out = [];
136829
+ collectTypeIdentifiers(node2, out, source);
136830
+ return out;
136831
+ }
136832
+ }
136833
+ return [];
136834
+ }
136835
+ function collectTypeIdentifiers(node2, out, source) {
136836
+ if (node2.type === "identifier") {
136837
+ out.push(source.slice(node2.startIndex, node2.endIndex));
136838
+ return;
136839
+ }
136840
+ for (let i = 0; i < node2.namedChildCount; i++) {
136841
+ const c2 = node2.namedChild(i);
136842
+ if (c2)
136843
+ collectTypeIdentifiers(c2, out, source);
136844
+ }
136845
+ }
136846
+ function synthesizeConstantClusterEnums(root, filePath, source, stringTable) {
136847
+ const named = [];
136848
+ for (const [name, node2] of stringTable) {
136849
+ const v = stringValue(node2, source, stringTable);
136850
+ if (v === null)
136851
+ continue;
136852
+ named.push({ name, value: v });
136853
+ }
136854
+ if (named.length < MIN_CLUSTER_MEMBERS)
136855
+ return [];
136856
+ named.sort((a, b) => a.value < b.value ? -1 : a.value > b.value ? 1 : 0);
136857
+ const clusters = [];
136858
+ let i = 0;
136859
+ while (i < named.length) {
136860
+ let j2 = i + 1;
136861
+ let prefix = named[i].value;
136862
+ while (j2 < named.length) {
136863
+ const next = commonPrefix(prefix, named[j2].value);
136864
+ if (next.length < MIN_PREFIX)
136865
+ break;
136866
+ prefix = next;
136867
+ j2++;
136868
+ }
136869
+ if (j2 - i >= MIN_CLUSTER_MEMBERS && j2 - i <= MAX_CLUSTER_MEMBERS && prefix.length >= MIN_PREFIX) {
136870
+ clusters.push({ prefix, members: named.slice(i, j2) });
136871
+ }
136872
+ i = j2 === i + 1 ? i + 1 : j2;
136873
+ }
136874
+ const out = [];
136875
+ for (const cluster of clusters) {
136876
+ const realMembers = cluster.members.filter((m) => m.value.length > cluster.prefix.length);
136877
+ if (realMembers.length < MIN_CLUSTER_MEMBERS)
136878
+ continue;
136879
+ let prefix = realMembers[0].value;
136880
+ for (let k = 1; k < realMembers.length; k++) {
136881
+ prefix = commonPrefix(prefix, realMembers[k].value);
136882
+ if (prefix.length < MIN_PREFIX)
136883
+ break;
136884
+ }
136885
+ if (prefix.length < MIN_PREFIX)
136886
+ continue;
136887
+ const trailingDelim = /[/\-_.:]$/.test(prefix);
136888
+ const allCleanTails = trailingDelim || realMembers.every((m) => /^[A-Za-z0-9_\-./:]+$/.test(m.value.slice(prefix.length)));
136889
+ if (!allCleanTails)
136890
+ continue;
136891
+ const trimmedName = prefix.replace(/[^A-Za-z0-9]+$/, "") || prefix;
136892
+ const firstNode = stringTable.get(realMembers[0].name);
136893
+ const lastNode = stringTable.get(realMembers[realMembers.length - 1].name);
136894
+ out.push({
136895
+ name: trimmedName,
136896
+ values: [...new Set(realMembers.map((m) => m.value))].sort(),
136897
+ shape: "py-constant-cluster",
136898
+ source: {
136899
+ filePath,
136900
+ lineStart: firstNode ? firstNode.startPosition.row + 1 : 1,
136901
+ lineEnd: lastNode ? lastNode.endPosition.row + 1 : 1
136902
+ }
136903
+ });
136904
+ }
136905
+ return out;
136906
+ }
136907
+ function commonPrefix(a, b) {
136908
+ const len = Math.min(a.length, b.length);
136909
+ let i = 0;
136910
+ while (i < len && a.charCodeAt(i) === b.charCodeAt(i))
136911
+ i++;
136912
+ return a.slice(0, i);
136913
+ }
136914
+ function synthesizeInstanceRegistryEnum(root, filePath, source) {
136915
+ const baseOf = /* @__PURE__ */ new Map();
136916
+ for (let i = 0; i < root.namedChildCount; i++) {
136917
+ const child = root.namedChild(i);
136918
+ const node2 = child?.type === "class_definition" ? child : child?.type === "decorated_definition" ? child.childForFieldName("definition") : null;
136919
+ if (node2?.type !== "class_definition")
136920
+ continue;
136921
+ const name = textOfField(node2, "name", source);
136922
+ const supers = node2.childForFieldName("superclasses");
136923
+ if (!name || !supers)
136924
+ continue;
136925
+ const first2 = supers.namedChild(0);
136926
+ if (first2?.type === "identifier")
136927
+ baseOf.set(name, source.slice(first2.startIndex, first2.endIndex));
136928
+ }
136929
+ const subclassCount = /* @__PURE__ */ new Map();
136930
+ for (const base of baseOf.values())
136931
+ subclassCount.set(base, (subclassCount.get(base) ?? 0) + 1);
136932
+ const categoricalBases = new Set([...subclassCount].filter(([, n]) => n >= 2).map(([b]) => b));
136933
+ if (categoricalBases.size === 0)
136934
+ return [];
136935
+ const resolveBase = (cls) => {
136936
+ let cur = cls;
136937
+ for (let hops = 0; hops < 8; hops++) {
136938
+ if (categoricalBases.has(cur))
136939
+ return cur;
136940
+ const next = baseOf.get(cur);
136941
+ if (!next)
136942
+ return null;
136943
+ cur = next;
136944
+ }
136945
+ return null;
136946
+ };
136947
+ const group = /* @__PURE__ */ new Map();
136948
+ const memberBase = /* @__PURE__ */ new Map();
136949
+ const deferred2 = [];
136950
+ for (let i = 0; i < root.namedChildCount; i++) {
136951
+ const stmt = root.namedChild(i);
136952
+ if (stmt?.type !== "expression_statement")
136953
+ continue;
136954
+ const assign = stmt.namedChild(0);
136955
+ if (assign?.type !== "assignment")
136956
+ continue;
136957
+ if (assign.childForFieldName("type"))
136958
+ continue;
136959
+ const left = assign.childForFieldName("left");
136960
+ if (left?.type !== "identifier")
136961
+ continue;
136962
+ const name = source.slice(left.startIndex, left.endIndex);
136963
+ if (!/^[A-Z][A-Z0-9_]*$/.test(name))
136964
+ continue;
136965
+ const right = assign.childForFieldName("right");
136966
+ if (!right)
136967
+ continue;
136968
+ if (right.type === "call") {
136969
+ const fn = right.childForFieldName("function");
136970
+ if (fn?.type !== "identifier")
136971
+ continue;
136972
+ const ctor = source.slice(fn.startIndex, fn.endIndex);
136973
+ const base = resolveBase(ctor);
136974
+ if (base) {
136975
+ if (!group.has(base))
136976
+ group.set(base, []);
136977
+ group.get(base).push(name);
136978
+ memberBase.set(name, base);
136979
+ }
136980
+ } else {
136981
+ deferred2.push({ name, rhs: right });
136982
+ }
136983
+ }
136984
+ for (const { name, rhs } of deferred2) {
136985
+ const ids = collectIdentifiers3(rhs, source);
136986
+ if (ids.length === 0)
136987
+ continue;
136988
+ const bases = new Set(ids.map((id) => memberBase.get(id)).filter((b) => !!b));
136989
+ if (bases.size === 1) {
136990
+ const base = [...bases][0];
136991
+ group.get(base).push(name);
136992
+ memberBase.set(name, base);
136993
+ }
136994
+ }
136995
+ const out = [];
136996
+ for (const [base, members] of group) {
136997
+ if (members.length < 3)
136998
+ continue;
136999
+ out.push(mkEnum2(base, members, "py-instance-registry", root, filePath));
137000
+ }
137001
+ return out;
137002
+ }
137003
+ function collectIdentifiers3(node2, source) {
137004
+ const out = [];
137005
+ const visit = (n) => {
137006
+ if (n.type === "identifier") {
137007
+ out.push(source.slice(n.startIndex, n.endIndex));
137008
+ return;
137009
+ }
137010
+ for (let i = 0; i < n.namedChildCount; i++) {
137011
+ const c2 = n.namedChild(i);
137012
+ if (c2)
137013
+ visit(c2);
137014
+ }
137015
+ };
137016
+ visit(node2);
136369
137017
  return out;
136370
137018
  }
136371
137019
  function extractEnumClass(node2, filePath, source) {
@@ -136442,6 +137090,22 @@ function extractAssignmentEnum(node2, filePath, source) {
136442
137090
  }
136443
137091
  return null;
136444
137092
  }
137093
+ if (right.type === "set" || right.type === "list") {
137094
+ const attrs = attributeSetValues(right, source);
137095
+ if (attrs) {
137096
+ return mkEnum2(name, attrs, right.type === "set" ? "py-set" : "py-list", node2, filePath);
137097
+ }
137098
+ }
137099
+ const diff = parseSetDifference(right, source);
137100
+ if (diff) {
137101
+ return {
137102
+ name,
137103
+ values: [],
137104
+ shape: "py-set-difference",
137105
+ source: { filePath, lineStart: node2.startPosition.row + 1, lineEnd: node2.endPosition.row + 1 },
137106
+ unresolved: diff
137107
+ };
137108
+ }
136445
137109
  if (!nameLooksLikeEnumConst2(name))
136446
137110
  return null;
136447
137111
  if (right.type === "set" || right.type === "list") {
@@ -136468,6 +137132,60 @@ function extractAssignmentEnum(node2, filePath, source) {
136468
137132
  function nameLooksLikeEnumConst2(name) {
136469
137133
  return ENUM_CONVENTION_NAME2.test(name) || ENUM_CONVENTION_SUFFIX2.test(name);
136470
137134
  }
137135
+ function attributeSetValues(node2, source) {
137136
+ const out = [];
137137
+ for (let i = 0; i < node2.namedChildCount; i++) {
137138
+ const c2 = node2.namedChild(i);
137139
+ if (!c2)
137140
+ continue;
137141
+ if (c2.type !== "attribute")
137142
+ return null;
137143
+ const attr = c2.childForFieldName("attribute");
137144
+ if (!attr)
137145
+ return null;
137146
+ out.push(source.slice(attr.startIndex, attr.endIndex));
137147
+ }
137148
+ return out.length >= 2 ? out : null;
137149
+ }
137150
+ function parseSetDifference(node2, source) {
137151
+ let n = node2;
137152
+ if (n.type === "call") {
137153
+ const fn = n.childForFieldName("function");
137154
+ const fnName = fn ? source.slice(fn.startIndex, fn.endIndex) : "";
137155
+ if (/^(list|set|frozenset|tuple)$/.test(fnName)) {
137156
+ const inner = n.childForFieldName("arguments")?.namedChild(0);
137157
+ if (inner)
137158
+ n = inner;
137159
+ }
137160
+ }
137161
+ if (n.type !== "binary_operator")
137162
+ return null;
137163
+ if (!source.slice(n.startIndex, n.endIndex).includes("-"))
137164
+ return null;
137165
+ const left = n.childForFieldName("left");
137166
+ const right = n.childForFieldName("right");
137167
+ if (!left || !right)
137168
+ return null;
137169
+ const base = enumOperandName(left, source);
137170
+ const minus = enumOperandName(right, source);
137171
+ return base && minus ? { base, minus } : null;
137172
+ }
137173
+ function enumOperandName(node2, source) {
137174
+ let n = node2;
137175
+ if (n.type === "call") {
137176
+ const inner = n.childForFieldName("arguments")?.namedChild(0);
137177
+ if (inner)
137178
+ n = inner;
137179
+ }
137180
+ if (n.type === "identifier")
137181
+ return source.slice(n.startIndex, n.endIndex);
137182
+ if (n.type === "attribute") {
137183
+ const a = n.childForFieldName("attribute");
137184
+ if (a)
137185
+ return source.slice(a.startIndex, a.endIndex);
137186
+ }
137187
+ return null;
137188
+ }
136471
137189
  function collectStringChildren(node2, source) {
136472
137190
  const out = [];
136473
137191
  for (let i = 0; i < node2.namedChildCount; i++) {
@@ -136480,24 +137198,6 @@ function collectStringChildren(node2, source) {
136480
137198
  }
136481
137199
  return out;
136482
137200
  }
136483
- function stringValue(node2, source) {
136484
- let content = "";
136485
- let sawContent = false;
136486
- for (let i = 0; i < node2.namedChildCount; i++) {
136487
- const c2 = node2.namedChild(i);
136488
- if (c2?.type === "string_content") {
136489
- content += source.slice(c2.startIndex, c2.endIndex);
136490
- sawContent = true;
136491
- } else if (c2?.type === "interpolation" || c2?.type === "format_specifier") {
136492
- return null;
136493
- }
136494
- }
136495
- if (sawContent)
136496
- return content;
136497
- const raw = source.slice(node2.startIndex, node2.endIndex);
136498
- const m = raw.match(/^[a-zA-Z]*('''|"""|'|")([\s\S]*)\1$/);
136499
- return m ? m[2] : null;
136500
- }
136501
137201
  function textOfField(node2, field, source) {
136502
137202
  const c2 = node2.childForFieldName(field);
136503
137203
  return c2 ? source.slice(c2.startIndex, c2.endIndex) : "";
@@ -136520,12 +137220,16 @@ function walk7(node2, visit) {
136520
137220
  walk7(c2, visit);
136521
137221
  }
136522
137222
  }
136523
- var ENUM_CONVENTION_NAME2, ENUM_CONVENTION_SUFFIX2;
137223
+ var ENUM_CONVENTION_NAME2, ENUM_CONVENTION_SUFFIX2, MIN_PREFIX, MIN_CLUSTER_MEMBERS, MAX_CLUSTER_MEMBERS;
136524
137224
  var init_py_enums = __esm({
136525
137225
  "packages/contract-verifier/dist/extractor/enum/py-enums.js"() {
136526
137226
  "use strict";
137227
+ init_py_string_resolver();
136527
137228
  ENUM_CONVENTION_NAME2 = /^(?:VALID|ALLOWED|KNOWN|ENUM)_/i;
136528
137229
  ENUM_CONVENTION_SUFFIX2 = /_(?:VALUES|SET|CLASSIFICATIONS|STATUSES|KINDS|TYPES|OPTIONS|CHOICES)$/i;
137230
+ MIN_PREFIX = 6;
137231
+ MIN_CLUSTER_MEMBERS = 3;
137232
+ MAX_CLUSTER_MEMBERS = 200;
136529
137233
  }
136530
137234
  });
136531
137235
 
@@ -136559,8 +137263,27 @@ async function extractEnumsFromDir(rootDir) {
136559
137263
  source: { filePath: entries[0].filePath, lineStart: 1, lineEnd: 1 }
136560
137264
  });
136561
137265
  }
136562
- const seen = /* @__PURE__ */ new Map();
137266
+ const norm = (s) => s.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
137267
+ const valuesByName = /* @__PURE__ */ new Map();
137268
+ for (const e of raw) {
137269
+ if (e.shape !== "py-set-difference")
137270
+ valuesByName.set(norm(e.name), e.values);
137271
+ }
136563
137272
  for (const e of raw) {
137273
+ if (e.shape !== "py-set-difference" || !e.unresolved)
137274
+ continue;
137275
+ const baseVals = valuesByName.get(norm(e.unresolved.base));
137276
+ const minusVals = valuesByName.get(norm(e.unresolved.minus));
137277
+ if (baseVals && minusVals) {
137278
+ const minusSet = new Set(minusVals.map((v) => v.replace(/[^A-Za-z0-9]/g, "").toLowerCase()));
137279
+ e.values = [...new Set(baseVals.filter((v) => !minusSet.has(v.replace(/[^A-Za-z0-9]/g, "").toLowerCase())))].sort();
137280
+ e.shape = "py-set";
137281
+ delete e.unresolved;
137282
+ }
137283
+ }
137284
+ const resolved = raw.filter((e) => e.shape !== "py-set-difference");
137285
+ const seen = /* @__PURE__ */ new Map();
137286
+ for (const e of resolved) {
136564
137287
  const key2 = `${e.name}|${e.values.join(",")}`;
136565
137288
  if (!seen.has(key2))
136566
137289
  seen.set(key2, e);
@@ -137254,6 +137977,13 @@ var init_ts_constants = __esm({
137254
137977
  // packages/contract-verifier/dist/extractor/constant/py-constants.js
137255
137978
  function extractPyConstantsFromFile(filePath, source, tree) {
137256
137979
  const out = [];
137980
+ const stringTable = collectStringConstantTable(tree.rootNode, source);
137981
+ walk10(tree.rootNode, (node2) => {
137982
+ if (node2.type === "class_definition") {
137983
+ out.push(...extractSettingsFields(node2, source, filePath, stringTable));
137984
+ }
137985
+ return true;
137986
+ });
137257
137987
  walk10(tree.rootNode, (node2) => {
137258
137988
  if (node2.type !== "assignment")
137259
137989
  return true;
@@ -137263,23 +137993,169 @@ function extractPyConstantsFromFile(filePath, source, tree) {
137263
137993
  const right = node2.childForFieldName("right");
137264
137994
  if (!right)
137265
137995
  return true;
137266
- const value = parseLiteral(right, source);
137267
- if (value === UNPARSEABLE2)
137996
+ const name = source.slice(left.startIndex, left.endIndex);
137997
+ const pos = { filePath, lineStart: node2.startPosition.row + 1, lineEnd: node2.endPosition.row + 1 };
137998
+ const value = parseLiteral(right, source, stringTable);
137999
+ if (value !== UNPARSEABLE2) {
138000
+ out.push({ name, value, shape: "const-literal", source: pos });
138001
+ return true;
138002
+ }
138003
+ if (!node2.childForFieldName("type"))
138004
+ return true;
138005
+ if (right.type !== "call")
138006
+ return true;
138007
+ const fn = right.childForFieldName("function");
138008
+ if (!fn)
138009
+ return true;
138010
+ if (!source.slice(fn.startIndex, fn.endIndex).endsWith("Field"))
138011
+ return true;
138012
+ const callArgs = right.childForFieldName("arguments");
138013
+ if (!callArgs)
138014
+ return true;
138015
+ let defaultVal = UNPARSEABLE2;
138016
+ const aliasStrings = [];
138017
+ for (let i = 0; i < callArgs.namedChildCount; i++) {
138018
+ const arg = callArgs.namedChild(i);
138019
+ if (arg?.type !== "keyword_argument")
138020
+ continue;
138021
+ const kwName = arg.childForFieldName("name");
138022
+ const kwVal = arg.childForFieldName("value");
138023
+ if (!kwName || !kwVal)
138024
+ continue;
138025
+ const kw = source.slice(kwName.startIndex, kwName.endIndex);
138026
+ if (kw === "default" && defaultVal === UNPARSEABLE2) {
138027
+ defaultVal = parseLiteral(kwVal, source, stringTable);
138028
+ } else if (kw === "validation_alias") {
138029
+ aliasStrings.push(...extractAliasChoiceStrings(kwVal, source, stringTable));
138030
+ }
138031
+ }
138032
+ if (defaultVal === UNPARSEABLE2)
137268
138033
  return true;
138034
+ out.push({ name, value: defaultVal, shape: "const-literal", source: pos });
138035
+ for (const alias of aliasStrings) {
138036
+ out.push({ name: alias, value: defaultVal, shape: "const-literal", source: pos });
138037
+ }
138038
+ return true;
138039
+ });
138040
+ return out;
138041
+ }
138042
+ function extractSettingsFields(classNode, source, filePath, stringTable) {
138043
+ const body = classNode.childForFieldName("body");
138044
+ if (!body)
138045
+ return [];
138046
+ const scope = deriveSettingsScope(body, source, stringTable);
138047
+ if (scope === null)
138048
+ return [];
138049
+ const out = [];
138050
+ for (let i = 0; i < body.namedChildCount; i++) {
138051
+ const stmt = body.namedChild(i);
138052
+ if (stmt?.type !== "expression_statement")
138053
+ continue;
138054
+ const assign = stmt.namedChild(0);
138055
+ if (assign?.type !== "assignment")
138056
+ continue;
138057
+ if (!assign.childForFieldName("type"))
138058
+ continue;
138059
+ const left = assign.childForFieldName("left");
138060
+ if (left?.type !== "identifier")
138061
+ continue;
138062
+ const fieldName = source.slice(left.startIndex, left.endIndex);
138063
+ if (fieldName === "model_config")
138064
+ continue;
138065
+ const right = assign.childForFieldName("right");
138066
+ if (!right)
138067
+ continue;
138068
+ const value = fieldDefaultValue(right, source, stringTable);
138069
+ if (value === UNPARSEABLE2)
138070
+ continue;
138071
+ const name = `${scope}_${fieldName}`.toUpperCase();
137269
138072
  out.push({
137270
- name: source.slice(left.startIndex, left.endIndex),
138073
+ name,
137271
138074
  value,
137272
- shape: "const-literal",
137273
- source: { filePath, lineStart: node2.startPosition.row + 1, lineEnd: node2.endPosition.row + 1 }
138075
+ shape: "settings-field",
138076
+ source: { filePath, lineStart: assign.startPosition.row + 1, lineEnd: assign.endPosition.row + 1 }
137274
138077
  });
137275
- return true;
137276
- });
138078
+ }
137277
138079
  return out;
137278
138080
  }
137279
- function parseLiteral(node2, source) {
138081
+ function deriveSettingsScope(body, source, stringTable) {
138082
+ for (let i = 0; i < body.namedChildCount; i++) {
138083
+ const stmt = body.namedChild(i);
138084
+ if (stmt?.type !== "expression_statement")
138085
+ continue;
138086
+ const assign = stmt.namedChild(0);
138087
+ if (assign?.type !== "assignment")
138088
+ continue;
138089
+ const left = assign.childForFieldName("left");
138090
+ if (left?.type !== "identifier")
138091
+ continue;
138092
+ if (source.slice(left.startIndex, left.endIndex) !== "model_config")
138093
+ continue;
138094
+ const right = assign.childForFieldName("right");
138095
+ if (right?.type !== "call")
138096
+ continue;
138097
+ const args = right.childForFieldName("arguments");
138098
+ if (!args)
138099
+ continue;
138100
+ for (let j2 = 0; j2 < args.namedChildCount; j2++) {
138101
+ const arg = args.namedChild(j2);
138102
+ if (arg?.type !== "keyword_argument")
138103
+ continue;
138104
+ const kw = arg.childForFieldName("name");
138105
+ const val = arg.childForFieldName("value");
138106
+ if (kw && val && source.slice(kw.startIndex, kw.endIndex) === "env_prefix" && val.type === "string") {
138107
+ const prefix = stringValue(val, source, stringTable);
138108
+ if (prefix)
138109
+ return prefix.replace(/_+$/, "").toLowerCase();
138110
+ }
138111
+ }
138112
+ for (let j2 = 0; j2 < args.namedChildCount; j2++) {
138113
+ const arg = args.namedChild(j2);
138114
+ if (arg?.type !== "tuple")
138115
+ continue;
138116
+ const parts = [];
138117
+ for (let k = 0; k < arg.namedChildCount; k++) {
138118
+ const el = arg.namedChild(k);
138119
+ if (el?.type === "string") {
138120
+ const v = stringValue(el, source, stringTable);
138121
+ if (v)
138122
+ parts.push(v);
138123
+ }
138124
+ }
138125
+ if (parts.length > 0)
138126
+ return parts.join("_").toLowerCase();
138127
+ }
138128
+ }
138129
+ return null;
138130
+ }
138131
+ function fieldDefaultValue(right, source, stringTable) {
138132
+ const direct = parseLiteral(right, source, stringTable);
138133
+ if (direct !== UNPARSEABLE2)
138134
+ return direct;
138135
+ if (right.type !== "call")
138136
+ return UNPARSEABLE2;
138137
+ const fn = right.childForFieldName("function");
138138
+ if (!fn || !source.slice(fn.startIndex, fn.endIndex).endsWith("Field"))
138139
+ return UNPARSEABLE2;
138140
+ const args = right.childForFieldName("arguments");
138141
+ if (!args)
138142
+ return UNPARSEABLE2;
138143
+ for (let i = 0; i < args.namedChildCount; i++) {
138144
+ const arg = args.namedChild(i);
138145
+ if (arg?.type !== "keyword_argument")
138146
+ continue;
138147
+ const kw = arg.childForFieldName("name");
138148
+ const val = arg.childForFieldName("value");
138149
+ if (kw && val && source.slice(kw.startIndex, kw.endIndex) === "default") {
138150
+ return parseLiteral(val, source, stringTable);
138151
+ }
138152
+ }
138153
+ return UNPARSEABLE2;
138154
+ }
138155
+ function parseLiteral(node2, source, stringTable) {
137280
138156
  switch (node2.type) {
137281
138157
  case "string": {
137282
- const v = stringValue2(node2, source);
138158
+ const v = stringValue(node2, source, stringTable);
137283
138159
  return v === null ? UNPARSEABLE2 : v;
137284
138160
  }
137285
138161
  case "integer":
@@ -137303,7 +138179,7 @@ function parseLiteral(node2, source) {
137303
138179
  const c2 = node2.namedChild(i);
137304
138180
  if (!c2)
137305
138181
  continue;
137306
- const v = parseLiteral(c2, source);
138182
+ const v = parseLiteral(c2, source, stringTable);
137307
138183
  if (v === UNPARSEABLE2)
137308
138184
  return UNPARSEABLE2;
137309
138185
  items.push(v);
@@ -137322,12 +138198,12 @@ function parseLiteral(node2, source) {
137322
138198
  continue;
137323
138199
  let key2 = null;
137324
138200
  if (keyNode.type === "string")
137325
- key2 = stringValue2(keyNode, source);
138201
+ key2 = stringValue(keyNode, source, stringTable);
137326
138202
  else if (keyNode.type === "identifier")
137327
138203
  key2 = source.slice(keyNode.startIndex, keyNode.endIndex);
137328
138204
  if (key2 === null)
137329
138205
  return UNPARSEABLE2;
137330
- const v = parseLiteral(valNode, source);
138206
+ const v = parseLiteral(valNode, source, stringTable);
137331
138207
  if (v === UNPARSEABLE2)
137332
138208
  return UNPARSEABLE2;
137333
138209
  obj[key2] = v;
@@ -137338,23 +138214,28 @@ function parseLiteral(node2, source) {
137338
138214
  return UNPARSEABLE2;
137339
138215
  }
137340
138216
  }
137341
- function stringValue2(node2, source) {
137342
- let content = "";
137343
- let sawContent = false;
137344
- for (let i = 0; i < node2.namedChildCount; i++) {
137345
- const c2 = node2.namedChild(i);
137346
- if (c2?.type === "string_content") {
137347
- content += source.slice(c2.startIndex, c2.endIndex);
137348
- sawContent = true;
137349
- } else if (c2?.type === "interpolation") {
137350
- return null;
138217
+ function extractAliasChoiceStrings(node2, source, stringTable) {
138218
+ if (node2.type !== "call")
138219
+ return [];
138220
+ const fn = node2.childForFieldName("function");
138221
+ if (!fn)
138222
+ return [];
138223
+ const fnText = source.slice(fn.startIndex, fn.endIndex);
138224
+ if (!fnText.endsWith("AliasChoices"))
138225
+ return [];
138226
+ const args = node2.childForFieldName("arguments");
138227
+ if (!args)
138228
+ return [];
138229
+ const result = [];
138230
+ for (let i = 0; i < args.namedChildCount; i++) {
138231
+ const c2 = args.namedChild(i);
138232
+ if (c2?.type === "string") {
138233
+ const v = stringValue(c2, source, stringTable);
138234
+ if (v !== null)
138235
+ result.push(v);
137351
138236
  }
137352
138237
  }
137353
- if (sawContent)
137354
- return content;
137355
- const raw = source.slice(node2.startIndex, node2.endIndex);
137356
- const m = raw.match(/^[a-zA-Z]*('''|"""|'|")([\s\S]*)\1$/);
137357
- return m ? m[2] : null;
138238
+ return result;
137358
138239
  }
137359
138240
  function walk10(node2, visit) {
137360
138241
  const recurse = visit(node2);
@@ -137370,6 +138251,7 @@ var UNPARSEABLE2;
137370
138251
  var init_py_constants = __esm({
137371
138252
  "packages/contract-verifier/dist/extractor/constant/py-constants.js"() {
137372
138253
  "use strict";
138254
+ init_py_string_resolver();
137373
138255
  UNPARSEABLE2 = Symbol("unparseable");
137374
138256
  }
137375
138257
  });
@@ -137855,7 +138737,7 @@ function readTransitionMap(node2, s) {
137855
138737
  for (const elem of v.namedChildren) {
137856
138738
  if (elem.type !== "string")
137857
138739
  return null;
137858
- const text = stringValue3(elem, s);
138740
+ const text = stringValue2(elem, s);
137859
138741
  if (text === null)
137860
138742
  return null;
137861
138743
  tos.push(text);
@@ -137894,7 +138776,7 @@ function readFieldAssignment(node2, s) {
137894
138776
  return null;
137895
138777
  if (rhs.type !== "string")
137896
138778
  return null;
137897
- const value = stringValue3(rhs, s);
138779
+ const value = stringValue2(rhs, s);
137898
138780
  if (value === null)
137899
138781
  return null;
137900
138782
  return {
@@ -138001,13 +138883,13 @@ function matchPair(member, literal, s) {
138001
138883
  return [{
138002
138884
  receiver: s.source.slice(obj.startIndex, obj.endIndex),
138003
138885
  field: s.source.slice(prop.startIndex, prop.endIndex),
138004
- value: stringValue3(literal, s) ?? ""
138886
+ value: stringValue2(literal, s) ?? ""
138005
138887
  }];
138006
138888
  }
138007
138889
  function stripQuotes(str2) {
138008
138890
  return str2.replace(/^['"]|['"]$/g, "");
138009
138891
  }
138010
- function stringValue3(node2, s) {
138892
+ function stringValue2(node2, s) {
138011
138893
  if (node2.type !== "string")
138012
138894
  return null;
138013
138895
  const childType = s.lang === "python" ? "string_content" : "string_fragment";
@@ -139170,7 +140052,11 @@ async function extractOperationsFromDir(rootDir) {
139170
140052
  await eachParsedSource(rootDir, (s) => {
139171
140053
  if (s.lang !== "python")
139172
140054
  return;
139173
- for (const op of extractFastApiOperationsFromFile(s.filePath, s.source, s.tree)) {
140055
+ const pythonOps = [
140056
+ ...extractFastApiOperationsFromFile(s.filePath, s.source, s.tree),
140057
+ ...extractStarletteRoutesFromFile(s.filePath, s.source, s.tree)
140058
+ ];
140059
+ for (const op of pythonOps) {
139174
140060
  if (!seen.has(op.identity)) {
139175
140061
  expressOps.push(op);
139176
140062
  seen.add(op.identity);
@@ -140402,7 +141288,9 @@ import { randomUUID as randomUUID19 } from "node:crypto";
140402
141288
  function compareEnum(input) {
140403
141289
  const { ref, contract, codeEnums } = input;
140404
141290
  const drifts = [];
140405
- const nameMatches = matchByName(contract, codeEnums, ref.identity);
141291
+ const matched = matchByName(contract, codeEnums, ref.identity);
141292
+ const valueOnlyMatch = matched.byName.length === 0;
141293
+ const nameMatches = matched.byName.length > 0 ? matched.byName : bestValueMatch(contract, matched.byValue);
140406
141294
  if (nameMatches.length === 0) {
140407
141295
  drifts.push({
140408
141296
  id: randomUUID19(),
@@ -140418,21 +141306,37 @@ function compareEnum(input) {
140418
141306
  codeSide: "<no match>"
140419
141307
  });
140420
141308
  } else {
141309
+ const specNorm = new Map(contract.values.map((v) => [normalizeValue(v), v]));
141310
+ const cloudOnlyValues = new Set((contract.triggerSubsets ?? []).filter((s) => isEnvironmentGatedSubset(s.name)).flatMap((s) => s.values.map(normalizeValue)));
141311
+ const byName = /* @__PURE__ */ new Map();
140421
141312
  for (const m of nameMatches) {
140422
- const specNorm = new Map(contract.values.map((v) => [normalizeValue(v), v]));
140423
- const codeNorm = new Map(m.values.map((v) => [normalizeValue(v), v]));
140424
- const missing = contract.values.filter((v) => !codeNorm.has(normalizeValue(v)));
140425
- const extra = m.values.filter((v) => !specNorm.has(normalizeValue(v)));
141313
+ const key2 = normalizeName(m.name);
141314
+ if (!byName.has(key2))
141315
+ byName.set(key2, { values: /* @__PURE__ */ new Set(), repr: m });
141316
+ const g = byName.get(key2);
141317
+ for (const v of m.values)
141318
+ g.values.add(normalizeValue(v));
141319
+ }
141320
+ const specNormSet = new Set(contract.values.map(normalizeValue));
141321
+ for (const g of byName.values()) {
141322
+ if (valueOnlyMatch && isStrictSubset(g.values, specNormSet))
141323
+ continue;
141324
+ const missing = contract.values.filter((v) => !g.values.has(normalizeValue(v)) && !cloudOnlyValues.has(normalizeValue(v)));
140426
141325
  for (const v of missing) {
140427
- drifts.push(mkValueDrift(ref, "missing-value", v, m, contract.values));
141326
+ drifts.push(mkValueDrift(ref, "missing-value", v, g.repr, contract.values));
140428
141327
  }
141328
+ }
141329
+ for (const m of nameMatches) {
141330
+ if (isSynthesizedEnumShape(m.shape))
141331
+ continue;
141332
+ const extra = m.values.filter((v) => !specNorm.has(normalizeValue(v)));
140429
141333
  for (const v of extra) {
140430
141334
  drifts.push(mkValueDrift(ref, "extra-value", v, m, contract.values));
140431
141335
  }
140432
141336
  }
140433
141337
  }
140434
141338
  for (const subset of contract.triggerSubsets ?? []) {
140435
- const subsetMatches = matchSubsetByName(subset.name, codeEnums);
141339
+ const subsetMatches = matchSubsetByName(subset.name, subset.values, codeEnums);
140436
141340
  if (subsetMatches.length === 0) {
140437
141341
  drifts.push({
140438
141342
  id: randomUUID19(),
@@ -140473,13 +141377,17 @@ function normalizeName(s) {
140473
141377
  }
140474
141378
  return n;
140475
141379
  }
141380
+ function isEnvironmentGatedSubset(name) {
141381
+ return normalizeName(name) === "cloudonly";
141382
+ }
140476
141383
  function matchByName(contract, codeEnums, specName) {
140477
141384
  const target = normalizeName(specName);
140478
- const out = [];
141385
+ const byName = [];
141386
+ const byValue = [];
140479
141387
  for (const e of codeEnums) {
140480
141388
  const codeName = normalizeName(e.name);
140481
141389
  if (codeName === target) {
140482
- out.push(e);
141390
+ byName.push(e);
140483
141391
  continue;
140484
141392
  }
140485
141393
  if (codeName.includes(target) || target.includes(codeName)) {
@@ -140490,7 +141398,7 @@ function matchByName(contract, codeEnums, specName) {
140490
141398
  }
140491
141399
  }
140492
141400
  if (valueSetOverlap(contract.values, e.values) >= 0.5) {
140493
- out.push(e);
141401
+ byName.push(e);
140494
141402
  continue;
140495
141403
  }
140496
141404
  }
@@ -140499,21 +141407,40 @@ function matchByName(contract, codeEnums, specName) {
140499
141407
  const overlap = valueSetOverlap(contract.values, e.values);
140500
141408
  const sizeDiff = Math.abs(contract.values.length - e.values.length);
140501
141409
  if (overlap >= 0.6 && sizeDiff <= 2) {
140502
- out.push(e);
141410
+ byValue.push(e);
140503
141411
  }
140504
141412
  } else if (minLen >= 2) {
140505
141413
  const overlap = valueSetOverlap(contract.values, e.values);
140506
141414
  const sizeDiff = Math.abs(contract.values.length - e.values.length);
140507
141415
  if (overlap === 1 && sizeDiff === 0) {
140508
- out.push(e);
141416
+ byValue.push(e);
140509
141417
  }
140510
141418
  }
140511
141419
  }
140512
- return out;
141420
+ return { byName, byValue };
141421
+ }
141422
+ function bestValueMatch(contract, byValue) {
141423
+ if (byValue.length <= 1)
141424
+ return byValue;
141425
+ let best = -1;
141426
+ for (const e of byValue)
141427
+ best = Math.max(best, valueSetOverlap(contract.values, e.values));
141428
+ return byValue.filter((e) => valueSetOverlap(contract.values, e.values) === best);
141429
+ }
141430
+ function isSynthesizedEnumShape(shape) {
141431
+ return shape === "sibling-id-literal" || shape === "py-instance-registry" || shape === "py-discriminated-union" || shape === "py-constant-cluster";
140513
141432
  }
140514
141433
  function normalizeValue(v) {
140515
141434
  return v.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
140516
141435
  }
141436
+ function isStrictSubset(codeValues, specNorm) {
141437
+ if (codeValues.size === 0 || codeValues.size >= specNorm.size)
141438
+ return false;
141439
+ for (const v of codeValues)
141440
+ if (!specNorm.has(v))
141441
+ return false;
141442
+ return true;
141443
+ }
140517
141444
  function valueSetOverlap(a, b) {
140518
141445
  const aSet = new Set(a.map(normalizeValue));
140519
141446
  const bSet = new Set(b.map(normalizeValue));
@@ -140521,9 +141448,21 @@ function valueSetOverlap(a, b) {
140521
141448
  const union = (/* @__PURE__ */ new Set([...aSet, ...bSet])).size;
140522
141449
  return union === 0 ? 0 : intersection / union;
140523
141450
  }
140524
- function matchSubsetByName(subsetName, codeEnums) {
141451
+ function matchSubsetByName(subsetName, subsetValues, codeEnums) {
140525
141452
  const target = normalizeName(subsetName);
140526
- return codeEnums.filter((e) => normalizeName(e.name) === target);
141453
+ const out = [];
141454
+ for (const e of codeEnums) {
141455
+ const codeName = normalizeName(e.name);
141456
+ if (codeName === target) {
141457
+ out.push(e);
141458
+ continue;
141459
+ }
141460
+ if (codeName.includes(target) || target.includes(codeName)) {
141461
+ if (valueSetOverlap(subsetValues, e.values) >= 0.5)
141462
+ out.push(e);
141463
+ }
141464
+ }
141465
+ return out;
140527
141466
  }
140528
141467
  function mkValueDrift(ref, kind, value, codeEnum, specValues) {
140529
141468
  const severity = kind === "missing-value" ? "high" : "medium";
@@ -140648,6 +141587,21 @@ function compareNamedConstant(input) {
140648
141587
  return false;
140649
141588
  });
140650
141589
  }
141590
+ if (matches.length === 0) {
141591
+ matches = codeConstants.filter((c2) => {
141592
+ if (c2.shape !== "settings-field")
141593
+ return false;
141594
+ const codeName = normalizeName2(c2.name);
141595
+ if (codeName.length < 8 || !target.endsWith(codeName))
141596
+ return false;
141597
+ return contract.expectedValue === void 0 || deepEqual(
141598
+ contract.expectedValue,
141599
+ c2.value,
141600
+ /*allowExtraCodeKeys*/
141601
+ true
141602
+ );
141603
+ });
141604
+ }
140651
141605
  if (matches.length === 0) {
140652
141606
  return [{
140653
141607
  id: randomUUID21(),
@@ -140891,6 +141845,8 @@ async function verify(opts) {
140891
141845
  if (!code2) {
140892
141846
  if (st2 === "planned" || st2 === "deferred" || st2 === "out-of-scope")
140893
141847
  continue;
141848
+ if (/^[A-Z]+ https?:\/\//.test(artifact.ref.identity))
141849
+ continue;
140894
141850
  drifts.push({
140895
141851
  id: cryptoRandomId(),
140896
141852
  type: "contract-drift",
@@ -142199,7 +143155,7 @@ function stripCodeFences(text) {
142199
143155
  }
142200
143156
  function cliTransport(opts = {}) {
142201
143157
  const bin = opts.bin ?? resolveClaudeBinary();
142202
- return (req) => new Promise((resolve9, reject) => {
143158
+ return (req) => new Promise((resolve10, reject) => {
142203
143159
  const modelArgs = [];
142204
143160
  if (req.model)
142205
143161
  modelArgs.push("--model", req.model);
@@ -142244,7 +143200,7 @@ function cliTransport(opts = {}) {
142244
143200
  reject(new Error("claude returned no text"));
142245
143201
  return;
142246
143202
  }
142247
- resolve9(text);
143203
+ resolve10(text);
142248
143204
  } catch (e) {
142249
143205
  reject(e instanceof Error ? e : new Error(String(e)));
142250
143206
  }
@@ -142378,7 +143334,7 @@ function checkClaudeAuth(binary2 = resolveClaudeBinary(), options = {}) {
142378
143334
  return Promise.resolve({ ok: false, reason: "not-found" });
142379
143335
  }
142380
143336
  const timeoutMs = options.timeoutMs ?? 6e4;
142381
- return new Promise((resolve9) => {
143337
+ return new Promise((resolve10) => {
142382
143338
  const proc = (0, import_cross_spawn2.spawn)(binary2, ["-p", "Reply with the single word: ok"], {
142383
143339
  stdio: ["ignore", "pipe", "pipe"],
142384
143340
  env: cleanClaudeEnv()
@@ -142390,7 +143346,7 @@ function checkClaudeAuth(binary2 = resolveClaudeBinary(), options = {}) {
142390
143346
  return;
142391
143347
  settled = true;
142392
143348
  clearTimeout(timer);
142393
- resolve9(r);
143349
+ resolve10(r);
142394
143350
  };
142395
143351
  const timer = setTimeout(() => {
142396
143352
  proc.kill("SIGKILL");
@@ -143088,7 +144044,7 @@ var WindowsService = class {
143088
144044
  const { Service } = nw;
143089
144045
  const logDir = path22.dirname(logPath);
143090
144046
  const truecourseHome = path22.join(os7.homedir(), ".truecourse");
143091
- return new Promise((resolve9, reject) => {
144047
+ return new Promise((resolve10, reject) => {
143092
144048
  const svc = new Service({
143093
144049
  name: SERVICE_DISPLAY_NAME,
143094
144050
  description: "TrueCourse Server",
@@ -143104,7 +144060,7 @@ var WindowsService = class {
143104
144060
  });
143105
144061
  svc.on("install", () => {
143106
144062
  svc.start();
143107
- resolve9();
144063
+ resolve10();
143108
144064
  });
143109
144065
  svc.on("error", (err) => reject(err));
143110
144066
  svc.install();
@@ -143113,13 +144069,13 @@ var WindowsService = class {
143113
144069
  async uninstall() {
143114
144070
  const nw = await this.getNodeWindows();
143115
144071
  const { Service } = nw;
143116
- return new Promise((resolve9, reject) => {
144072
+ return new Promise((resolve10, reject) => {
143117
144073
  const svc = new Service({
143118
144074
  name: SERVICE_DISPLAY_NAME,
143119
144075
  script: ""
143120
144076
  // Not needed for uninstall
143121
144077
  });
143122
- svc.on("uninstall", () => resolve9());
144078
+ svc.on("uninstall", () => resolve10());
143123
144079
  svc.on("error", (err) => reject(err));
143124
144080
  svc.uninstall();
143125
144081
  });
@@ -144583,6 +145539,27 @@ not narrow the set.
144583
145539
  substitute for \`immutable\`. If a derived/server-computed field is also frozen
144584
145540
  after creation, emit BOTH \`computed-at\` AND \`immutable\`.
144585
145541
 
145542
+ **NEVER infer \`immutable\` from what a field IS \u2014 only from what the spec SAYS
145543
+ about changing it.** The following do NOT imply immutability, alone or combined:
145544
+
145545
+ - The field is required (\`Required? yes\` in a field table).
145546
+ - The field is an identifier (\`id\`, \`uuid\`, "identifier of this X").
145547
+ - The field is a timestamp ("when the event happened", \`occurred\`, \`createdAt\`).
145548
+ - The field is client-provided or server-assigned (provenance is not mutability).
145549
+
145550
+ Counter-example \u2014 a field table like:
145551
+
145552
+ | Name | Type | Required? | Description |
145553
+ | -------- | ------ | --------- | ---------------------------------------- |
145554
+ | occurred | String | yes | When the event happened |
145555
+ | id | String | yes | Client-provided identifier of this event |
145556
+
145557
+ asserts NOTHING about mutability. Both fields get plain \`field occurred: string {}\`
145558
+ / \`field id: string {}\` \u2014 no \`immutable\`, even though an id "sounds" immutable.
145559
+ Emitting un-stated \`immutable\` creates a false drift against every codebase whose
145560
+ model simply doesn't freeze the field. If the spec is silent on mutability, the
145561
+ contract is silent on mutability.
145562
+
144586
145563
  # Enum extraction \u2014 required whenever spec defines an enum
144587
145564
 
144588
145565
  Whenever the spec text contains any of:
@@ -144605,6 +145582,27 @@ data document. If \`Entity:Customer\` references \`Enum:LoyaltyTier\` and the sa
144605
145582
  slice contains "LoyaltyTier values: standard, silver, gold", emit BOTH the entity
144606
145583
  fragment AND the enum fragment.
144607
145584
 
145585
+ **An enum is a closed set of DATA VALUES the code compares against** (the
145586
+ string/number literals a field is set to or checked against) \u2014 NOT a catalog of
145587
+ code symbols a caller picks between. Before emitting, check what the listed items
145588
+ ARE. Emit nothing when the list is:
145589
+
145590
+ - **Implementation / plugin classes** \u2014 e.g. "run launchers: \`DefaultRunLauncher\`,
145591
+ \`DockerRunLauncher\`, \`K8sRunLauncher\`", storage backends, compute-log managers.
145592
+ These are swappable extension points (a user can add their own); the items are
145593
+ class names, not a closed value set.
145594
+ - **API functions, decorators, or methods** \u2014 e.g. "asset decorators: \`asset\`,
145595
+ \`multi_asset\`, \`graph_asset\`". These are symbols a user calls, not values.
145596
+ - **An incidental excerpt** \u2014 a few items quoted inside a troubleshooting,
145597
+ example, or how-to passage ("relevant event types: \u2026") rather than a
145598
+ definitional "the valid values of X are \u2026". A subset mentioned in passing is
145599
+ not an enum definition.
145600
+
145601
+ Discriminator: if the items are **names of code symbols** (classes / functions a
145602
+ caller selects), do NOT emit an enum. Only emit when the items are **literal data
145603
+ values** the code stores or compares against (config keys, status strings, tag
145604
+ keys, numeric codes).
145605
+
144608
145606
  **Never reference an enum without defining it.** Every \`Enum:X\` identifier you
144609
145607
  emit (in \`field: Enum:X\` or \`states Enum:X\`) MUST have a matching \`enum X { \u2026 }\`
144610
145608
  artifact somewhere in the same slice (or you must assume another slice provides
@@ -144934,6 +145932,42 @@ Don't emit constants whose value is a function call, expression, or
144934
145932
  external reference \u2014 only literal values (strings, numbers, booleans,
144935
145933
  arrays of literals, flat object literals of literals).
144936
145934
 
145935
+ **Don't extract constants from customization examples.** A constant asserts
145936
+ "the code MUST hold this value". A doc that shows users how to CUSTOMIZE or
145937
+ OVERRIDE something is asserting the opposite \u2014 "you can put whatever value
145938
+ you want here" \u2014 and its example values are illustrations, not obligations.
145939
+ Skip a candidate value when EITHER signal is present:
145940
+
145941
+ 1. **Context signal**: the page title or enclosing section heading describes
145942
+ a customization/override mechanism \u2014 "Customize \u2026", "Override \u2026",
145943
+ "\u2026 template", "\u2026 variables", "Configure your own \u2026", "Example
145944
+ configuration".
145945
+ 2. **Shape signal**: the value sits inside a JSON-schema-style variables
145946
+ block, i.e. an object of the form
145947
+ \`"<name>": { "title": \u2026, "description": \u2026, "default": <value>, "type": \u2026 }\`.
145948
+ That quadruple is a schema DECLARING an overridable variable; the
145949
+ \`"default"\` there is a starting point the user is expected to change.
145950
+
145951
+ Counter-example \u2014 a page titled "Customize Base Job Templates" containing:
145952
+
145953
+ \`\`\`json
145954
+ "cpu_request": {
145955
+ "title": "CPU Request",
145956
+ "description": "CPU allocation to request for this pod",
145957
+ "default": "100m",
145958
+ "type": "string"
145959
+ }
145960
+ \`\`\`
145961
+
145962
+ emits NO constant. Both signals fire: the page is a customization guide, and
145963
+ the value is a variables-schema \`default\`. Extracting
145964
+ \`constant cpu_request { expected-value "100m" }\` would demand every codebase
145965
+ hard-code the example \u2014 a guaranteed false drift.
145966
+
145967
+ By contrast, prose like "the scheduler polls every 15 seconds" or "the API
145968
+ version is \`v2\`" in a behavioral spec section IS an assertion about the
145969
+ system \u2014 extract those normally.
145970
+
144937
145971
  # ArchitectureDecision \u2014 extract when the spec/ADR fixes a platform choice
144938
145972
 
144939
145973
  A spec or ADR that records a system-wide technology choice \u2014 "we use
@@ -145431,6 +146465,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
145431
146465
  ".cache",
145432
146466
  "coverage"
145433
146467
  ]);
146468
+ var SKIP_DIR_PROBE = "__tc_skipdir_probe__.md";
145434
146469
  var PREVIEW_LINE_LIMIT = 200;
145435
146470
  function discoverDocs(rootDir, opts = {}) {
145436
146471
  const previewLines = opts.previewLines ?? PREVIEW_LINE_LIMIT;
@@ -145445,11 +146480,12 @@ function discoverDocs(rootDir, opts = {}) {
145445
146480
  }
145446
146481
  entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
145447
146482
  for (const entry of entries) {
145448
- if (SKIP_DIRS.has(entry.name))
146483
+ const full = path26.join(dir, entry.name);
146484
+ if (SKIP_DIRS.has(entry.name) && !(entry.isDirectory() && tcIgnore.reincludes(path26.join(full, SKIP_DIR_PROBE)))) {
145449
146485
  continue;
146486
+ }
145450
146487
  if (entry.name.startsWith(".") && entry.name !== ".")
145451
146488
  continue;
145452
- const full = path26.join(dir, entry.name);
145453
146489
  if (tcIgnore.ignores(full))
145454
146490
  continue;
145455
146491
  if (entry.isDirectory()) {
@@ -147308,14 +148344,14 @@ async function explainConflicts(repoRoot6, conflicts2, opts = {}) {
147308
148344
  opts.onStart?.(conflicts2.length);
147309
148345
  let active = 0;
147310
148346
  let cursor = 0;
147311
- await new Promise((resolve9) => {
148347
+ await new Promise((resolve10) => {
147312
148348
  const launch = () => {
147313
148349
  while (active < concurrency && cursor < conflicts2.length) {
147314
148350
  const conflict = conflicts2[cursor++];
147315
148351
  if (conflict.explanation) {
147316
148352
  opts.onDone?.();
147317
148353
  if (cursor >= conflicts2.length && active === 0)
147318
- resolve9();
148354
+ resolve10();
147319
148355
  continue;
147320
148356
  }
147321
148357
  active++;
@@ -147323,13 +148359,13 @@ async function explainConflicts(repoRoot6, conflicts2, opts = {}) {
147323
148359
  opts.onDone?.();
147324
148360
  active--;
147325
148361
  if (cursor >= conflicts2.length && active === 0)
147326
- resolve9();
148362
+ resolve10();
147327
148363
  else
147328
148364
  launch();
147329
148365
  });
147330
148366
  }
147331
148367
  if (cursor >= conflicts2.length && active === 0)
147332
- resolve9();
148368
+ resolve10();
147333
148369
  };
147334
148370
  launch();
147335
148371
  });
@@ -147562,7 +148598,7 @@ async function resolveConflicts(repoRoot6, conflicts2, opts = {}) {
147562
148598
  const out = [];
147563
148599
  let cursor = 0;
147564
148600
  let active = 0;
147565
- await new Promise((resolve9) => {
148601
+ await new Promise((resolve10) => {
147566
148602
  const launch = () => {
147567
148603
  while (active < concurrency && cursor < conflicts2.length) {
147568
148604
  const conflict = conflicts2[cursor++];
@@ -147581,13 +148617,13 @@ async function resolveConflicts(repoRoot6, conflicts2, opts = {}) {
147581
148617
  }).finally(() => {
147582
148618
  active--;
147583
148619
  if (cursor >= conflicts2.length && active === 0)
147584
- resolve9();
148620
+ resolve10();
147585
148621
  else
147586
148622
  launch();
147587
148623
  });
147588
148624
  }
147589
148625
  if (cursor >= conflicts2.length && active === 0)
147590
- resolve9();
148626
+ resolve10();
147591
148627
  };
147592
148628
  launch();
147593
148629
  });
@@ -147807,7 +148843,7 @@ async function filterByRelevance(repoRoot6, docs, opts = {}) {
147807
148843
  const verdicts = /* @__PURE__ */ new Map();
147808
148844
  let cursor = 0;
147809
148845
  let active = 0;
147810
- await new Promise((resolve9) => {
148846
+ await new Promise((resolve10) => {
147811
148847
  const launch = () => {
147812
148848
  while (active < concurrency && cursor < docs.length) {
147813
148849
  const doc = docs[cursor++];
@@ -147815,7 +148851,7 @@ async function filterByRelevance(repoRoot6, docs, opts = {}) {
147815
148851
  verdicts.set(doc.path, { path: doc.path, include: true, reason: "manual include" });
147816
148852
  markDone();
147817
148853
  if (cursor >= docs.length && active === 0)
147818
- resolve9();
148854
+ resolve10();
147819
148855
  continue;
147820
148856
  }
147821
148857
  active++;
@@ -147831,13 +148867,13 @@ async function filterByRelevance(repoRoot6, docs, opts = {}) {
147831
148867
  markDone();
147832
148868
  active--;
147833
148869
  if (cursor >= docs.length && active === 0)
147834
- resolve9();
148870
+ resolve10();
147835
148871
  else
147836
148872
  launch();
147837
148873
  });
147838
148874
  }
147839
148875
  if (cursor >= docs.length && active === 0)
147840
- resolve9();
148876
+ resolve10();
147841
148877
  };
147842
148878
  launch();
147843
148879
  });
@@ -154574,7 +155610,7 @@ async function runHooksRun() {
154574
155610
 
154575
155611
  // tools/cli/src/index.ts
154576
155612
  var program2 = new Command();
154577
- program2.name("truecourse").version("0.6.6").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
155613
+ program2.name("truecourse").version("0.6.7-next.1").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
154578
155614
  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) => {
154579
155615
  if (options.service && options.console) {
154580
155616
  console.error("error: --service and --console are mutually exclusive");