codegraph-engine 3.0.0__1-py3-none-any.whl

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 (130) hide show
  1. codegraph/__init__.py +37 -0
  2. codegraph/agent.py +26 -0
  3. codegraph/agent_brain.py +2212 -0
  4. codegraph/agent_capabilities.py +4058 -0
  5. codegraph/agent_rules.py +531 -0
  6. codegraph/api_drift.py +455 -0
  7. codegraph/architecture.py +371 -0
  8. codegraph/async_queue/__init__.py +50 -0
  9. codegraph/async_queue/detectors/__init__.py +48 -0
  10. codegraph/async_queue/detectors/celery.py +160 -0
  11. codegraph/async_queue/detectors/kafka.py +168 -0
  12. codegraph/async_queue/detectors/rabbitmq.py +116 -0
  13. codegraph/async_queue/detectors/redis.py +130 -0
  14. codegraph/async_queue/detectors/sqs.py +135 -0
  15. codegraph/async_queue/models.py +161 -0
  16. codegraph/async_queue/otel_models.py +74 -0
  17. codegraph/async_queue/otel_parser.py +337 -0
  18. codegraph/async_queue/reconciler.py +427 -0
  19. codegraph/async_queue/scanner.py +291 -0
  20. codegraph/async_queue/traversal.py +447 -0
  21. codegraph/audit.py +106 -0
  22. codegraph/binding_resolver.py +616 -0
  23. codegraph/branch_comparison.py +197 -0
  24. codegraph/cache.py +95 -0
  25. codegraph/change_impact.py +547 -0
  26. codegraph/cli.py +2436 -0
  27. codegraph/cli_output.py +205 -0
  28. codegraph/config.py +45 -0
  29. codegraph/constraints.py +238 -0
  30. codegraph/context.py +2182 -0
  31. codegraph/context_freshness.py +351 -0
  32. codegraph/database/__init__.py +72 -0
  33. codegraph/database/extractor.py +3651 -0
  34. codegraph/database/interrogation.py +1494 -0
  35. codegraph/database/models.py +310 -0
  36. codegraph/database/schema_drift.py +862 -0
  37. codegraph/epistemic.py +200 -0
  38. codegraph/errors.py +282 -0
  39. codegraph/evidence/__init__.py +15 -0
  40. codegraph/evidence/citations.py +418 -0
  41. codegraph/evidence_contract.py +486 -0
  42. codegraph/frameworks.py +960 -0
  43. codegraph/frameworks_nestjs.py +502 -0
  44. codegraph/frameworks_nextjs.py +337 -0
  45. codegraph/frameworks_react.py +457 -0
  46. codegraph/freshness.py +335 -0
  47. codegraph/frontend_html.py +109 -0
  48. codegraph/git.py +362 -0
  49. codegraph/git_state.py +545 -0
  50. codegraph/graph/__init__.py +46 -0
  51. codegraph/graph/models.py +56 -0
  52. codegraph/graph/traversal.py +1993 -0
  53. codegraph/indexing/__init__.py +24 -0
  54. codegraph/indexing/classifier.py +564 -0
  55. codegraph/indexing/indexer.py +2578 -0
  56. codegraph/indexing/models.py +427 -0
  57. codegraph/indexing/parser.py +3404 -0
  58. codegraph/indexing/pool.py +136 -0
  59. codegraph/indexing/scanner.py +211 -0
  60. codegraph/indexing/telemetry.py +326 -0
  61. codegraph/indexing/templates.py +418 -0
  62. codegraph/indexing/test_framework.py +116 -0
  63. codegraph/installer.py +1702 -0
  64. codegraph/interrogation.py +2355 -0
  65. codegraph/llm/__init__.py +3 -0
  66. codegraph/llm/base.py +15 -0
  67. codegraph/llm/context.py +20 -0
  68. codegraph/mcp/__init__.py +3 -0
  69. codegraph/mcp/server.py +1476 -0
  70. codegraph/mcp_diagnostics.py +882 -0
  71. codegraph/memory/__init__.py +3 -0
  72. codegraph/memory/store.py +46 -0
  73. codegraph/models.py +310 -0
  74. codegraph/monorepo.py +925 -0
  75. codegraph/observability.py +262 -0
  76. codegraph/optimizer.py +1014 -0
  77. codegraph/planner.py +417 -0
  78. codegraph/process_lifecycle.py +1153 -0
  79. codegraph/py.typed +1 -0
  80. codegraph/query_expansion.py +199 -0
  81. codegraph/ranking.py +408 -0
  82. codegraph/refactor/__init__.py +36 -0
  83. codegraph/refactor/models.py +114 -0
  84. codegraph/refactor/renamer.py +844 -0
  85. codegraph/refactor/transactions.py +414 -0
  86. codegraph/resolver.py +3968 -0
  87. codegraph/resources/__init__.py +46 -0
  88. codegraph/resources/cache.py +117 -0
  89. codegraph/resources/coalescer.py +83 -0
  90. codegraph/resources/debouncer.py +98 -0
  91. codegraph/resources/governor.py +219 -0
  92. codegraph/resources/monitor.py +102 -0
  93. codegraph/resources/policy.py +123 -0
  94. codegraph/retrieval_policy.py +320 -0
  95. codegraph/roblox.py +2174 -0
  96. codegraph/route_composer.py +363 -0
  97. codegraph/route_topology.py +172 -0
  98. codegraph/runtime/__init__.py +51 -0
  99. codegraph/runtime/collector.py +155 -0
  100. codegraph/runtime/db_watcher.py +191 -0
  101. codegraph/runtime/ingestor.py +692 -0
  102. codegraph/runtime/models.py +130 -0
  103. codegraph/runtime/port_inspector.py +304 -0
  104. codegraph/runtime/reconciliation.py +492 -0
  105. codegraph/runtime/runner.py +353 -0
  106. codegraph/runtime/trace_linker.py +175 -0
  107. codegraph/search/__init__.py +25 -0
  108. codegraph/search/hybrid.py +846 -0
  109. codegraph/search/semantic.py +28 -0
  110. codegraph/security/__init__.py +51 -0
  111. codegraph/security/paths.py +189 -0
  112. codegraph/security/redaction.py +633 -0
  113. codegraph/semantic_conflicts.py +270 -0
  114. codegraph/semantic_decorators.py +372 -0
  115. codegraph/structural_diff.py +542 -0
  116. codegraph/styles.py +144 -0
  117. codegraph/symbol_history.py +277 -0
  118. codegraph/target_resolver.py +348 -0
  119. codegraph/task.py +637 -0
  120. codegraph/tool_selection_eval.py +1158 -0
  121. codegraph/tsconfig.py +236 -0
  122. codegraph/ui/__init__.py +6 -0
  123. codegraph/ui/server.py +755 -0
  124. codegraph/watcher.py +323 -0
  125. codegraph_engine-3.0.0.dist-info/METADATA +859 -0
  126. codegraph_engine-3.0.0.dist-info/RECORD +130 -0
  127. codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
  128. codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
  129. codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
  130. codegraph_engine-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,502 @@
1
+ """Deterministic NestJS framework and dependency injection analyzer.
2
+
3
+ Inspects TypeScript/JavaScript classes for:
4
+ - @Controller('prefix') class decorators and HTTP method decorators (@Get, @Post, @Put, @Delete, @Patch, @Options, @Head, @All).
5
+ - Constructor dependency injection: typed parameters (e.g. `private readonly service: InvoicesService`)
6
+ and @Inject('TOKEN') / @Inject(TokenClass) parameter decorators.
7
+ - @Module({ controllers: [...], providers: [...], imports: [...] }) module declarations.
8
+
9
+ Emits:
10
+ - RouteDetection records for HTTP endpoints.
11
+ - BindingRef records with expr_kind='NESTJS_INJECTS' (for DI) and expr_kind='NESTJS_CONTROLLER' / 'DI_PROVIDES'.
12
+ - RouterMountDetection records for module-controller wiring.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import re
18
+ from dataclasses import dataclass, field
19
+
20
+ from codegraph.frameworks import (
21
+ RouteDetection,
22
+ RouterMountDetection,
23
+ join_route_prefixes,
24
+ normalize_route_path,
25
+ )
26
+ from codegraph.indexing.models import BindingRef, build_canonical_id
27
+
28
+ _NESTJS_HTTP_METHODS: dict[str, str] = {
29
+ "get": "GET",
30
+ "post": "POST",
31
+ "put": "PUT",
32
+ "delete": "DELETE",
33
+ "patch": "PATCH",
34
+ "options": "OPTIONS",
35
+ "head": "HEAD",
36
+ "all": "ANY",
37
+ }
38
+
39
+ _TS_PRIMITIVE_TYPES = frozenset({
40
+ "string",
41
+ "number",
42
+ "boolean",
43
+ "any",
44
+ "void",
45
+ "unknown",
46
+ "object",
47
+ "never",
48
+ "null",
49
+ "undefined",
50
+ "symbol",
51
+ "bigint",
52
+ "function",
53
+ "record",
54
+ "array",
55
+ "promise",
56
+ })
57
+
58
+ # Matches @Controller('path') or @Controller()
59
+ _CONTROLLER_DEC_RE = re.compile(
60
+ r"@Controller\s*\(\s*(?:['\"`]([^'\"`]*)['\"`])?\s*\)"
61
+ )
62
+
63
+ # Matches @Module({ ... })
64
+ _MODULE_DEC_RE = re.compile(r"@Module\s*\(")
65
+
66
+ # Matches @Injectable()
67
+ _INJECTABLE_DEC_RE = re.compile(r"@Injectable\s*\(")
68
+
69
+ # Matches @Get('path'), @Post(), etc.
70
+ _METHOD_DEC_RE = re.compile(
71
+ r"@(Get|Post|Put|Delete|Patch|Options|Head|All)\s*\(\s*(?:['\"`]([^'\"`]*)['\"`])?\s*\)",
72
+ re.IGNORECASE,
73
+ )
74
+
75
+ # Matches class declaration
76
+ _CLASS_DECL_RE = re.compile(
77
+ r"\b(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)"
78
+ )
79
+
80
+ # Matches method declaration inside class
81
+ _METHOD_DECL_RE = re.compile(
82
+ r"^\s*(?:public\s+|private\s+|protected\s+)?(?:async\s+)?(?:static\s+)?(?:get\s+|set\s+)?([A-Za-z_$][\w$]*)\s*\("
83
+ )
84
+
85
+ # Matches constructor declaration
86
+ _CONSTRUCTOR_START_RE = re.compile(r"\bconstructor\s*\(")
87
+
88
+ # Matches @Inject('TOKEN') or @Inject(TOKEN) inside constructor param
89
+ _INJECT_PARAM_DEC_RE = re.compile(
90
+ r"@Inject\s*\(\s*(?:['\"`]([^'\"`]*)['\"`]|([A-Za-z_$][\w$]*))\s*\)"
91
+ )
92
+
93
+
94
+ @dataclass
95
+ class NestjsAnalysisResult:
96
+ routes: list[RouteDetection] = field(default_factory=list)
97
+ mounts: list[RouterMountDetection] = field(default_factory=list)
98
+ bindings: list[BindingRef] = field(default_factory=list)
99
+
100
+
101
+ def _split_constructor_params(params_text: str) -> list[str]:
102
+ """Split comma-separated constructor parameters respecting nested generics, parens, brackets, braces."""
103
+ params: list[str] = []
104
+ current: list[str] = []
105
+ depth_paren = 0
106
+ depth_bracket = 0
107
+ depth_brace = 0
108
+ depth_angle = 0
109
+ in_quote = False
110
+ quote_char = ""
111
+
112
+ for ch in params_text:
113
+ if in_quote:
114
+ current.append(ch)
115
+ if ch == quote_char:
116
+ in_quote = False
117
+ continue
118
+ if ch in ('"', "'", "`"):
119
+ in_quote = True
120
+ quote_char = ch
121
+ current.append(ch)
122
+ continue
123
+
124
+ if ch == "(":
125
+ depth_paren += 1
126
+ elif ch == ")":
127
+ depth_paren = max(0, depth_paren - 1)
128
+ elif ch == "[":
129
+ depth_bracket += 1
130
+ elif ch == "]":
131
+ depth_bracket = max(0, depth_bracket - 1)
132
+ elif ch == "{":
133
+ depth_brace += 1
134
+ elif ch == "}":
135
+ depth_brace = max(0, depth_brace - 1)
136
+ elif ch == "<":
137
+ depth_angle += 1
138
+ elif ch == ">":
139
+ depth_angle = max(0, depth_angle - 1)
140
+
141
+ if (
142
+ ch == ","
143
+ and depth_paren == 0
144
+ and depth_bracket == 0
145
+ and depth_brace == 0
146
+ and depth_angle == 0
147
+ ):
148
+ p = "".join(current).strip()
149
+ if p:
150
+ params.append(p)
151
+ current = []
152
+ else:
153
+ current.append(ch)
154
+
155
+ p = "".join(current).strip()
156
+ if p:
157
+ params.append(p)
158
+ return params
159
+
160
+
161
+ def _extract_module_array(body: str, key: str) -> list[str]:
162
+ """Extract list of identifiers or objects from a @Module({ key: [...] }) configuration."""
163
+ m = re.search(rf"\b{key}\s*:\s*\[([\s\S]*?)\]", body)
164
+ if not m:
165
+ return []
166
+ items_raw = m.group(1)
167
+ results: list[str] = []
168
+ tokens: list[str] = []
169
+ current: list[str] = []
170
+ brace_depth = 0
171
+ for ch in items_raw:
172
+ if ch == "{":
173
+ brace_depth += 1
174
+ elif ch == "}":
175
+ brace_depth = max(0, brace_depth - 1)
176
+ if ch == "," and brace_depth == 0:
177
+ item = "".join(current).strip()
178
+ if item:
179
+ tokens.append(item)
180
+ current = []
181
+ else:
182
+ current.append(ch)
183
+ item = "".join(current).strip()
184
+ if item:
185
+ tokens.append(item)
186
+
187
+ for clean in tokens:
188
+ clean = re.sub(r"//.*$", "", clean).strip()
189
+ if not clean or clean.startswith("{"):
190
+ continue
191
+ ident_m = re.match(r"^([A-Za-z_$][\w$]*)", clean)
192
+ if ident_m:
193
+ results.append(ident_m.group(1))
194
+ return results
195
+
196
+
197
+ def _extract_custom_providers(body: str) -> list[tuple[str, str]]:
198
+ """Extract { provide: 'TOKEN' | Type, useClass: Impl } custom providers."""
199
+ providers: list[tuple[str, str]] = []
200
+ for m in re.finditer(r"\{\s*provide\s*:\s*(['\"`]?[\w$]+['\"`]?)\s*,\s*useClass\s*:\s*([A-Za-z_$][\w$]*)\s*,?\s*\}", body, re.DOTALL):
201
+ token = m.group(1).strip("'\"`")
202
+ use_class = m.group(2)
203
+ if token and use_class:
204
+ providers.append((token, use_class))
205
+ return providers
206
+
207
+
208
+ def analyze_nestjs_file(
209
+ content: str,
210
+ file_path: str,
211
+ module: str,
212
+ ) -> NestjsAnalysisResult:
213
+ """Analyze a TypeScript/JavaScript file for NestJS controllers, routes, modules, and constructor DI."""
214
+ res = NestjsAnalysisResult()
215
+
216
+ if "@" not in content:
217
+ return res
218
+
219
+ lines = content.splitlines()
220
+ total_lines = len(lines)
221
+
222
+ # 1. Parse @Module definitions
223
+ for mod_m in _MODULE_DEC_RE.finditer(content):
224
+ start_idx = mod_m.start()
225
+ line_no = content[:start_idx].count("\n") + 1
226
+ # Extract the object literal argument for @Module({ ... })
227
+ brace_start = content.find("{", start_idx)
228
+ if brace_start != -1:
229
+ depth = 1
230
+ idx = brace_start + 1
231
+ while idx < len(content) and depth > 0:
232
+ if content[idx] == "{":
233
+ depth += 1
234
+ elif content[idx] == "}":
235
+ depth -= 1
236
+ idx += 1
237
+ mod_body = content[brace_start + 1 : idx - 1]
238
+
239
+ # Find following class name
240
+ post_content = content[idx:]
241
+ cls_m = _CLASS_DECL_RE.search(post_content)
242
+ mod_class = cls_m.group(1) if cls_m else "AppModule"
243
+ mod_canon = build_canonical_id(module, "", mod_class)
244
+
245
+ # Controllers
246
+ controllers = _extract_module_array(mod_body, "controllers")
247
+ for ctrl in controllers:
248
+ res.bindings.append(
249
+ BindingRef(
250
+ target_name=mod_canon,
251
+ source_expr=ctrl,
252
+ file_path=file_path,
253
+ line=line_no,
254
+ scope=mod_class,
255
+ expr_kind="NESTJS_CONTROLLER",
256
+ base_expr="nestjs_module",
257
+ attr_name="controller",
258
+ )
259
+ )
260
+
261
+ # Class providers
262
+ class_providers = _extract_module_array(mod_body, "providers")
263
+ for prov in class_providers:
264
+ res.bindings.append(
265
+ BindingRef(
266
+ target_name=mod_canon,
267
+ source_expr=prov,
268
+ file_path=file_path,
269
+ line=line_no,
270
+ scope=mod_class,
271
+ expr_kind="DI_PROVIDES",
272
+ base_expr="nestjs_module",
273
+ attr_name="provider",
274
+ )
275
+ )
276
+
277
+ # Custom providers
278
+ custom_providers = _extract_custom_providers(mod_body)
279
+ for token, use_class in custom_providers:
280
+ res.bindings.append(
281
+ BindingRef(
282
+ target_name=token,
283
+ source_expr=use_class,
284
+ file_path=file_path,
285
+ line=line_no,
286
+ scope=mod_class,
287
+ expr_kind="DI_PROVIDES",
288
+ base_expr="nestjs_provider",
289
+ attr_name="useClass",
290
+ subscript_key=json.dumps({"token": token, "provider": use_class}),
291
+ )
292
+ )
293
+ res.bindings.append(
294
+ BindingRef(
295
+ target_name=mod_canon,
296
+ source_expr=use_class,
297
+ file_path=file_path,
298
+ line=line_no,
299
+ scope=mod_class,
300
+ expr_kind="DI_PROVIDES",
301
+ base_expr="nestjs_module",
302
+ attr_name="custom_provider",
303
+ )
304
+ )
305
+
306
+ # 2. Parse Classes, Controllers, Route Methods, and Constructor DI
307
+ i = 0
308
+ pending_class_decorators: list[tuple[str, str, int]] = [] # (decorator_name, arg, line_no)
309
+
310
+ while i < total_lines:
311
+ line = lines[i]
312
+ stripped = line.strip()
313
+
314
+ # Check for class-level decorators
315
+ ctrl_m = _CONTROLLER_DEC_RE.search(stripped)
316
+ if ctrl_m:
317
+ prefix = ctrl_m.group(1) or ""
318
+ pending_class_decorators.append(("Controller", prefix, i + 1))
319
+ i += 1
320
+ continue
321
+
322
+ if _INJECTABLE_DEC_RE.search(stripped):
323
+ pending_class_decorators.append(("Injectable", "", i + 1))
324
+ i += 1
325
+ continue
326
+
327
+ # Check for class declaration
328
+ class_m = _CLASS_DECL_RE.search(stripped)
329
+ if class_m:
330
+ class_name = class_m.group(1)
331
+ class_canon = build_canonical_id(module, "", class_name)
332
+
333
+ # Determine if this class is a Controller
334
+ is_controller = False
335
+ controller_prefix = ""
336
+ for dec_name, dec_arg, _ in pending_class_decorators:
337
+ if dec_name == "Controller":
338
+ is_controller = True
339
+ controller_prefix = dec_arg
340
+ break
341
+
342
+ pending_class_decorators.clear()
343
+
344
+ # Now scan inside the class body for methods, decorators, and constructor
345
+ # Find class closing brace
346
+ class_depth = line.count("{") - line.count("}")
347
+ j = i + 1
348
+ pending_method_decorators: list[tuple[str, str, int]] = []
349
+
350
+ while j < total_lines and class_depth > 0:
351
+ inner_line = lines[j]
352
+ inner_stripped = inner_line.strip()
353
+ class_depth += inner_line.count("{") - inner_line.count("}")
354
+
355
+ # Method decorators: @Get, @Post, etc.
356
+ method_dec_m = _METHOD_DEC_RE.search(inner_stripped)
357
+ if method_dec_m:
358
+ m_http = method_dec_m.group(1).lower()
359
+ m_path = method_dec_m.group(2) or ""
360
+ pending_method_decorators.append((m_http, m_path, j + 1))
361
+ j += 1
362
+ continue
363
+
364
+ # Constructor declaration
365
+ if _CONSTRUCTOR_START_RE.search(inner_stripped):
366
+ const_line = j + 1
367
+ # Collect constructor parameter block up to matching ')'
368
+ paren_depth = inner_line.count("(") - inner_line.count(")")
369
+ const_lines = [inner_line]
370
+ k = j + 1
371
+ while k < total_lines and paren_depth > 0:
372
+ const_lines.append(lines[k])
373
+ paren_depth += lines[k].count("(") - lines[k].count(")")
374
+ k += 1
375
+
376
+ full_const_sig = " ".join(c_line.strip() for c_line in const_lines)
377
+ start_paren = full_const_sig.find("(")
378
+ if start_paren != -1:
379
+ p_depth = 1
380
+ idx_c = start_paren + 1
381
+ while idx_c < len(full_const_sig) and p_depth > 0:
382
+ if full_const_sig[idx_c] == "(":
383
+ p_depth += 1
384
+ elif full_const_sig[idx_c] == ")":
385
+ p_depth -= 1
386
+ idx_c += 1
387
+ raw_params = full_const_sig[start_paren + 1 : idx_c - 1].strip()
388
+ if raw_params:
389
+ params = _split_constructor_params(raw_params)
390
+ for param_str in params:
391
+ # Check for @Inject('TOKEN') or @Inject(TOKEN)
392
+ inject_m = _INJECT_PARAM_DEC_RE.search(param_str)
393
+ inject_token = (
394
+ inject_m.group(1) or inject_m.group(2)
395
+ if inject_m
396
+ else None
397
+ )
398
+
399
+ # Clean out decorators and access modifiers to extract param_name: Type
400
+ clean_p = _INJECT_PARAM_DEC_RE.sub("", param_str).strip()
401
+ clean_p = re.sub(
402
+ r"\b(private|public|protected|readonly)\b",
403
+ "",
404
+ clean_p,
405
+ ).strip()
406
+
407
+ # Match name: Type
408
+ name_type_m = re.match(
409
+ r"^([A-Za-z_$][\w$]*)\s*(?:\??\s*:\s*([^=,]+))?",
410
+ clean_p,
411
+ )
412
+ if name_type_m:
413
+ p_name = name_type_m.group(1)
414
+ p_type_raw = name_type_m.group(2)
415
+ p_type = p_type_raw.strip().split("<")[0].strip() if p_type_raw else ""
416
+
417
+ if inject_token:
418
+ res.bindings.append(
419
+ BindingRef(
420
+ target_name=class_canon,
421
+ source_expr=inject_token,
422
+ file_path=file_path,
423
+ line=const_line,
424
+ scope=class_name,
425
+ expr_kind="NESTJS_INJECTS",
426
+ base_expr="nestjs_inject",
427
+ attr_name=p_name,
428
+ subscript_key=json.dumps({
429
+ "param": p_name,
430
+ "type": p_type,
431
+ "token": inject_token,
432
+ }),
433
+ )
434
+ )
435
+ elif p_type and p_type.lower() not in _TS_PRIMITIVE_TYPES:
436
+ res.bindings.append(
437
+ BindingRef(
438
+ target_name=class_canon,
439
+ source_expr=p_type,
440
+ file_path=file_path,
441
+ line=const_line,
442
+ scope=class_name,
443
+ expr_kind="NESTJS_INJECTS",
444
+ base_expr="nestjs",
445
+ attr_name=p_name,
446
+ subscript_key=json.dumps({
447
+ "param": p_name,
448
+ "type": p_type,
449
+ }),
450
+ )
451
+ )
452
+ j = k
453
+ continue
454
+
455
+ # Method declaration inside controller
456
+ meth_m = _METHOD_DECL_RE.match(inner_stripped)
457
+ if meth_m and is_controller:
458
+ method_name = meth_m.group(1)
459
+
460
+ if method_name not in ("constructor", "if", "for", "while", "switch"):
461
+ for http_m_raw, path_arg, dec_line in pending_method_decorators:
462
+ method_upper = _NESTJS_HTTP_METHODS.get(http_m_raw.lower(), "GET")
463
+ full_route = join_route_prefixes(controller_prefix, path_arg)
464
+ norm_route = normalize_route_path(full_route, "nestjs")
465
+ handler_canon = f"{module}.{class_name}.{method_name}"
466
+
467
+ res.routes.append(
468
+ RouteDetection(
469
+ framework="nestjs",
470
+ http_method=method_upper,
471
+ route_path=full_route,
472
+ normalized_route=norm_route,
473
+ handler_name=method_name,
474
+ handler_canonical_id=handler_canon,
475
+ file_path=file_path,
476
+ line=dec_line,
477
+ confidence="HIGH",
478
+ resolution_status="RESOLVED",
479
+ evidence=f"NestJS @{http_m_raw.capitalize()}('{path_arg}') on {class_name}.{method_name}",
480
+ module=module,
481
+ router_name=class_name,
482
+ )
483
+ )
484
+
485
+ pending_method_decorators.clear()
486
+
487
+ elif not inner_stripped.startswith("@"):
488
+ if not inner_stripped.startswith("//") and inner_stripped:
489
+ pending_method_decorators.clear()
490
+
491
+ j += 1
492
+
493
+ i = j
494
+ continue
495
+
496
+ # Non-decorator, non-blank statement outside class clears pending decorators
497
+ if not stripped.startswith("@") and not stripped.startswith("//") and stripped:
498
+ pending_class_decorators.clear()
499
+
500
+ i += 1
501
+
502
+ return res