agent-code-guard 0.1.0__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 (40) hide show
  1. agent_code_guard/__init__.py +1 -0
  2. agent_code_guard/analysis/__init__.py +13 -0
  3. agent_code_guard/analysis/adapters.py +608 -0
  4. agent_code_guard/analysis/errors.py +13 -0
  5. agent_code_guard/analysis/facts.py +101 -0
  6. agent_code_guard/analysis/language_specs.py +82 -0
  7. agent_code_guard/analysis/pipeline.py +37 -0
  8. agent_code_guard/analysis/provider.py +45 -0
  9. agent_code_guard/analysis/regions.py +108 -0
  10. agent_code_guard/code_guard.py +236 -0
  11. agent_code_guard/config_validation.py +90 -0
  12. agent_code_guard/file_selection.py +228 -0
  13. agent_code_guard/guards/__init__.py +1 -0
  14. agent_code_guard/guards/callable_size.py +79 -0
  15. agent_code_guard/guards/complexity.py +94 -0
  16. agent_code_guard/guards/loc.py +235 -0
  17. agent_code_guard/guards/markdown_document_size.py +66 -0
  18. agent_code_guard/guards/markdown_section_size.py +66 -0
  19. agent_code_guard/guards/nesting.py +109 -0
  20. agent_code_guard/markdown/__init__.py +6 -0
  21. agent_code_guard/markdown/facts.py +27 -0
  22. agent_code_guard/markdown/scanner.py +109 -0
  23. agent_code_guard/path_matching.py +25 -0
  24. agent_code_guard/reporting.py +11 -0
  25. agent_code_guard/result_model.py +128 -0
  26. agent_code_guard/skill_distribution.py +96 -0
  27. agent_code_guard-0.1.0.data/data/share/agent-code-guard/skill/LICENSE.txt +21 -0
  28. agent_code_guard-0.1.0.data/data/share/agent-code-guard/skill/SKILL.md +138 -0
  29. agent_code_guard-0.1.0.data/data/share/agent-code-guard/skill/agents/openai.yaml +8 -0
  30. agent_code_guard-0.1.0.data/data/share/agent-code-guard/skill/references/callable-size-policy.md +39 -0
  31. agent_code_guard-0.1.0.data/data/share/agent-code-guard/skill/references/complexity-policy.md +39 -0
  32. agent_code_guard-0.1.0.data/data/share/agent-code-guard/skill/references/loc-policy.md +40 -0
  33. agent_code_guard-0.1.0.data/data/share/agent-code-guard/skill/references/markdown-size-policy.md +16 -0
  34. agent_code_guard-0.1.0.data/data/share/agent-code-guard/skill/references/nesting-policy.md +37 -0
  35. agent_code_guard-0.1.0.dist-info/METADATA +206 -0
  36. agent_code_guard-0.1.0.dist-info/RECORD +40 -0
  37. agent_code_guard-0.1.0.dist-info/WHEEL +5 -0
  38. agent_code_guard-0.1.0.dist-info/entry_points.txt +2 -0
  39. agent_code_guard-0.1.0.dist-info/licenses/LICENSE +21 -0
  40. agent_code_guard-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ """Agent Code Guard production package."""
@@ -0,0 +1,13 @@
1
+ """Production source/container and syntax-fact pipeline."""
2
+
3
+ from .errors import AnalysisError, ProviderUnavailableError, SyntaxAnalysisError
4
+ from .facts import AnalysisFacts, CallableFact, CallableKey, ControlFlowFact, DecisionFact, FileFacts, SourcePoint, SourceRange
5
+ from .pipeline import analyze_files
6
+ from .provider import TreeSitterProvider
7
+ from .regions import ExecutableRegion, is_applicable
8
+
9
+ __all__ = [
10
+ "AnalysisError", "AnalysisFacts", "CallableFact", "CallableKey", "ControlFlowFact", "DecisionFact",
11
+ "ExecutableRegion", "FileFacts", "ProviderUnavailableError", "SourcePoint", "SourceRange",
12
+ "SyntaxAnalysisError", "TreeSitterProvider", "analyze_files", "is_applicable",
13
+ ]
@@ -0,0 +1,608 @@
1
+ """Language-specific Tree-sitter extraction into normalized immutable facts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Iterator
6
+
7
+ from .facts import CallableFact, CallableKey, ControlFlowFact, DecisionFact, SourceRange
8
+ from .language_specs import (
9
+ CALLABLE_TYPES, CONTROL_CATEGORIES, CONTROL_TYPES, DECISION_CATEGORIES, DECISION_TYPES,
10
+ OPAQUE_LAMBDA_TYPES,
11
+ )
12
+ from .regions import ExecutableRegion
13
+
14
+
15
+ def extract_facts(root, region: ExecutableRegion) -> tuple[tuple[CallableFact, ...], tuple[ControlFlowFact, ...], tuple[DecisionFact, ...]]:
16
+ nodes = [node for node in _walk(root) if node.type in CALLABLE_TYPES[region.language] and _has_body(node, region.language)]
17
+ identities = {_node_key(node): _identity(node, region) for node in nodes}
18
+ ranges = {_node_key(node): _callable_range(node, region) for node in nodes}
19
+ keys = {
20
+ node_key: CallableKey(region.original_path, region.language, identity, ranges[node_key])
21
+ for node_key, identity in identities.items()
22
+ }
23
+ callables: list[CallableFact] = []
24
+ controls: list[ControlFlowFact] = []
25
+ decisions: list[DecisionFact] = []
26
+ for node in nodes:
27
+ identity = identities[_node_key(node)]
28
+ parent_node = next((ancestor for ancestor in _ancestors(node) if _node_key(ancestor) in identities), None)
29
+ if parent_node is None:
30
+ containing = [candidate for candidate in nodes if candidate is not node
31
+ and ranges[_node_key(candidate)].start.byte_offset <= ranges[_node_key(node)].start.byte_offset
32
+ and ranges[_node_key(candidate)].end.byte_offset >= ranges[_node_key(node)].end.byte_offset]
33
+ parent_node = min(containing, key=lambda candidate: ranges[_node_key(candidate)].physical_loc, default=None)
34
+ node_key = _node_key(node)
35
+ parent_key = keys.get(_node_key(parent_node)) if parent_node is not None else None
36
+ callables.append(CallableFact(
37
+ region.original_path, region.language, identity, ranges[node_key],
38
+ identities.get(_node_key(parent_node)) if parent_node is not None else None,
39
+ "callback" if _is_anonymous_callable(node, region) else ("nested" if parent_node else "callable"),
40
+ keys[node_key], parent_key,
41
+ ))
42
+ extracted_controls, extracted_decisions = _structural_facts(node, keys[node_key], region)
43
+ controls.extend(extracted_controls)
44
+ decisions.extend(extracted_decisions)
45
+ key = lambda fact: (fact.source_range.start.byte_offset, fact.source_range.end.byte_offset)
46
+ return tuple(sorted(callables, key=key)), tuple(sorted(controls, key=key)), tuple(sorted(decisions, key=key))
47
+
48
+
49
+ def _structural_facts(callable_node, callable_key: CallableKey, region: ExecutableRegion):
50
+ controls: list[ControlFlowFact] = []
51
+ decisions: list[DecisionFact] = []
52
+ language = region.language
53
+
54
+ def visit(node, parent_control: SourceRange | None) -> None:
55
+ for child in node.named_children:
56
+ if child.type in CALLABLE_TYPES[language] or child.type in OPAQUE_LAMBDA_TYPES[language]:
57
+ continue
58
+ child_range = region.original_range(child)
59
+ next_parent = parent_control
60
+ if child.type in CONTROL_TYPES[language] and _is_meaningful_control(child, language):
61
+ increases = not (child.type in {"elif_clause", "else_if_clause"} or _is_else_if(child, language))
62
+ controls.append(ControlFlowFact(
63
+ callable_key.identity, callable_key, _control_category(child.type, language), child.type,
64
+ child_range, parent_control, increases,
65
+ ))
66
+ if increases:
67
+ next_parent = child_range
68
+ if child.type in DECISION_TYPES[language] and not _is_default_branch(child, region.source, language):
69
+ decisions.append(DecisionFact(
70
+ callable_key.identity, callable_key, DECISION_CATEGORIES.get(child.type, child.type), child.type, child_range,
71
+ ))
72
+ for provider_kind, arm_range in _extra_switch_arm_ranges(child, language, region):
73
+ decisions.append(DecisionFact(callable_key.identity, callable_key, "switch_arm", provider_kind, arm_range))
74
+ for arm_range in _php_switch_arm_ranges(child, language, region):
75
+ decisions.append(DecisionFact(
76
+ callable_key.identity, callable_key, "switch_arm", "case_statement", arm_range,
77
+ ))
78
+ guard = _pattern_guard(child, language)
79
+ if guard is not None:
80
+ decisions.append(DecisionFact(callable_key.identity, callable_key, "pattern_guard", guard.type,
81
+ region.original_range(guard)))
82
+ visit(child, next_parent)
83
+
84
+ for structural_root in _structural_roots(callable_node, language):
85
+ visit(structural_root, None)
86
+ return controls, decisions
87
+
88
+
89
+ def _walk(node) -> Iterator:
90
+ yield node
91
+ for child in node.named_children:
92
+ yield from _walk(child)
93
+
94
+
95
+ def _node_key(node) -> tuple[str, int, int]:
96
+ return node.type, node.start_byte, node.end_byte
97
+
98
+
99
+ def _has_body(node, language: str) -> bool:
100
+ if language in {"typescript", "tsx"} and node.type in {"function_declaration", "method_definition"}:
101
+ return node.child_by_field_name("body") is not None
102
+ if language == "swift" and node.type == "protocol_function_declaration":
103
+ return any(child.type == "statements" for child in node.named_children)
104
+ if language == "dart" and node.type in {"function_signature", "method_signature"}:
105
+ return _dart_body(node) is not None and not any(parent.type == "lambda_expression" for parent in _ancestors(node))
106
+ return True
107
+
108
+
109
+ def _range_end_node(node, language: str):
110
+ return _dart_body(node) if language == "dart" and _dart_body(node) is not None else node
111
+
112
+
113
+ def _callable_range(node, region: ExecutableRegion) -> SourceRange:
114
+ """Snapshot provider points once before mapping them to original source."""
115
+ start_row, start_column = _range_start_node(node, region.language).start_point
116
+ end_row, end_column = _range_end_node(node, region.language).end_point
117
+ return SourceRange(
118
+ region.original_point(start_row, start_column),
119
+ region.original_point(end_row, end_column),
120
+ )
121
+
122
+
123
+ def _structural_roots(node, language: str):
124
+ body = _dart_body(node) if language == "dart" else None
125
+ return (body,) if body is not None else (node,)
126
+
127
+
128
+ def _dart_body(node):
129
+ if node.type not in {"function_signature", "method_signature"}:
130
+ return None
131
+ sibling = node.next_named_sibling
132
+ return sibling if sibling is not None and sibling.type == "function_body" else None
133
+
134
+
135
+ def _range_start_node(node, language: str):
136
+ if language == "python" and node.parent and node.parent.type == "decorated_definition":
137
+ return node.parent
138
+ if language in {"javascript", "typescript", "tsx"} and node.type in {"arrow_function", "function_expression"}:
139
+ declarator = _ancestor(node, "variable_declarator")
140
+ if declarator and declarator.child_by_field_name("value") == node:
141
+ return declarator.parent if declarator.parent and declarator.parent.type in {"lexical_declaration", "variable_declaration"} else declarator
142
+ if language in {"typescript", "tsx"} and node.type == "method_definition":
143
+ first = node
144
+ previous = node.prev_named_sibling
145
+ while previous and previous.type == "decorator":
146
+ first, previous = previous, previous.prev_named_sibling
147
+ return first
148
+ if language == "cpp" and node.parent and node.parent.type == "template_declaration":
149
+ return node.parent
150
+ if language in {"cpp", "php", "swift", "dart", "rust"} and _is_closure(node, language):
151
+ owner = _assigned_closure_owner(node, language)
152
+ if owner is not None:
153
+ return owner
154
+ if language == "swift" and node.type == "protocol_function_declaration" and node.prev_named_sibling:
155
+ previous = node.prev_named_sibling
156
+ if previous.type == "protocol_function_declaration" and previous.child_by_field_name("name") is not None:
157
+ return previous
158
+ return node
159
+
160
+
161
+ def _identity(node, region: ExecutableRegion) -> str:
162
+ if region.language in {"javascript", "typescript", "tsx"}:
163
+ return _javascript_identity(node, region)
164
+ if region.language in {"cpp", "rust", "php", "swift", "dart"}:
165
+ return _second_wave_identity(node, region)
166
+ if node.type in MAINSTREAM_LAMBDA_TYPES[region.language]:
167
+ return _mainstream_lambda_identity(node, region)
168
+ source, language = region.source, region.language
169
+ parts = [_name(node, language, source)]
170
+ owner_types = {
171
+ "python": {"class_definition", "function_definition"}, "go": set(),
172
+ "kotlin": {"class_declaration", "object_declaration", "function_declaration"},
173
+ "csharp": {"namespace_declaration", "file_scoped_namespace_declaration", "class_declaration", "struct_declaration", "record_declaration", "method_declaration", "constructor_declaration", "local_function_statement"},
174
+ "java": {"class_declaration", "record_declaration", "enum_declaration", "method_declaration", "constructor_declaration"},
175
+ }[language]
176
+ for current in _ancestors(node):
177
+ if current.type in owner_types:
178
+ name = _name_node(current, language)
179
+ if name:
180
+ parts.append(_text(name, source))
181
+ if language == "go":
182
+ receiver_type = _go_receiver_type(node, source)
183
+ if receiver_type:
184
+ parts.append(receiver_type)
185
+ parts.append(region.original_path.stem if language == "python" else _package_or_namespace(node, language, source))
186
+ return ".".join(reversed([part for part in parts if part]))
187
+
188
+
189
+ def _javascript_identity(node, region: ExecutableRegion) -> str:
190
+ source = region.source
191
+ name = node.child_by_field_name("name")
192
+ if node.type in {"arrow_function", "function_expression"}:
193
+ declarator = _ancestor(node, "variable_declarator")
194
+ if declarator and declarator.child_by_field_name("value") == node:
195
+ name = declarator.child_by_field_name("name")
196
+ parts = [_text(name, source) if name else _callback_name(node, region)]
197
+ for current in _ancestors(node):
198
+ owner = None
199
+ if current.type == "class_declaration":
200
+ owner = current.child_by_field_name("name")
201
+ elif current.type == "method_definition" and current is not node:
202
+ owner = current.child_by_field_name("name")
203
+ elif current.type in {"function_declaration", "arrow_function", "function_expression"} and current is not node:
204
+ lexical = _javascript_lexical_name(current, source)
205
+ if lexical:
206
+ parts.append(lexical)
207
+ if owner:
208
+ parts.append(_text(owner, source))
209
+ if node.type == "method_definition" and not any(value.type == "class_declaration" for value in _ancestors(node)):
210
+ object_name = _object_assignment_name(node, source)
211
+ if object_name:
212
+ parts.append(object_name)
213
+ parts.append(region.original_path.stem)
214
+ return ".".join(reversed(parts))
215
+
216
+
217
+ def _name(node, language: str, source: bytes) -> str:
218
+ name = _name_node(node, language)
219
+ if name:
220
+ return _text(name, source)
221
+ if language in {"kotlin", "csharp", "java"} and "constructor" in node.type:
222
+ for owner in _ancestors(node):
223
+ if owner.type in {"class_declaration", "object_declaration", "struct_declaration", "record_declaration", "enum_declaration"}:
224
+ owner_name = _name_node(owner, language)
225
+ if owner_name:
226
+ return _text(owner_name, source)
227
+ return "<anonymous>"
228
+
229
+
230
+ def _name_node(node, language: str):
231
+ name = node.child_by_field_name("name")
232
+ if name is None and language == "kotlin":
233
+ name = next((child for child in node.named_children if child.type in {"simple_identifier", "type_identifier"}), None)
234
+ return name
235
+
236
+
237
+ def _go_receiver_type(method_node, source: bytes) -> str | None:
238
+ receiver = method_node.child_by_field_name("receiver")
239
+ if receiver is None:
240
+ return None
241
+ words = _text(receiver, source).replace("(", "").replace(")", "").replace("*", "").split()
242
+ return words[-1] if words else "receiver"
243
+
244
+
245
+ def _second_wave_identity(node, region: ExecutableRegion) -> str:
246
+ language, source = region.language, region.source
247
+ name = _second_wave_name(node, language, source)
248
+ parts = [name or _callback_name(node, region)]
249
+ owner_types = {
250
+ "cpp": {"namespace_definition", "class_specifier", "struct_specifier", "union_specifier", "function_definition"},
251
+ "rust": {"trait_item", "impl_item", "function_item"},
252
+ "php": {"namespace_definition", "class_declaration", "trait_declaration", "interface_declaration", "function_definition", "method_declaration"},
253
+ "swift": {"class_declaration", "struct_declaration", "protocol_declaration", "function_declaration", "init_declaration"},
254
+ "dart": {"class_definition", "function_signature", "constructor_signature", "lambda_expression"},
255
+ }[language]
256
+ for current in _ancestors(node):
257
+ if current.type not in owner_types:
258
+ continue
259
+ owner = _second_wave_name(current, language, source)
260
+ if owner:
261
+ parts.append(owner)
262
+ parts.append(region.original_path.stem)
263
+ return ".".join(reversed(parts))
264
+
265
+
266
+ def _second_wave_name(node, language: str, source: bytes) -> str | None:
267
+ name = node.child_by_field_name("name")
268
+ if language == "cpp" and node.type == "function_definition":
269
+ declarator = node.child_by_field_name("declarator")
270
+ name = _deep_named_child(declarator, {"identifier", "field_identifier", "destructor_name", "operator_name"})
271
+ elif language == "cpp" and node.type == "lambda_expression":
272
+ name = _assigned_name(node, language)
273
+ elif language == "rust" and node.type == "closure_expression":
274
+ name = _assigned_name(node, language)
275
+ elif language == "php" and node.type in {"arrow_function", "anonymous_function"}:
276
+ name = _assigned_name(node, language)
277
+ elif language == "swift" and node.type == "init_declaration":
278
+ return "init"
279
+ elif language == "swift" and node.type == "protocol_function_declaration" and name is None:
280
+ previous = node.prev_named_sibling
281
+ name = previous.child_by_field_name("name") if previous is not None else None
282
+ elif language == "swift" and node.type == "lambda_literal":
283
+ name = _assigned_name(node, language)
284
+ elif language == "dart" and node.type in {"function_expression", "lambda_expression"}:
285
+ signature = next((child for child in node.named_children if child.type == "function_signature"), None)
286
+ name = signature.child_by_field_name("name") if signature is not None else _assigned_name(node, language)
287
+ elif language == "dart" and node.type == "method_signature":
288
+ return _dart_method_name(node, source)
289
+ elif language == "dart" and node.type == "class_definition":
290
+ name = node.child_by_field_name("name")
291
+ elif language == "rust" and node.type == "impl_item":
292
+ name = node.child_by_field_name("type")
293
+ if name is None and language == "swift" and node.type == "class_declaration":
294
+ name = next((child for child in node.named_children if child.type in {"type_identifier", "user_type"}), None)
295
+ return _text(name, source).lstrip("$") if name is not None else None
296
+
297
+
298
+ def _dart_method_name(method_signature, source: bytes) -> str | None:
299
+ signature = next((child for child in method_signature.named_children if child.type in {
300
+ "function_signature", "constructor_signature", "factory_constructor_signature",
301
+ }), None)
302
+ if signature is None:
303
+ return None
304
+ if signature.type in {"constructor_signature", "factory_constructor_signature"}:
305
+ return _dart_constructor_name(signature, source)
306
+ name = signature.child_by_field_name("name")
307
+ return _text(name, source).lstrip("$") if name is not None else None
308
+
309
+
310
+ def _dart_constructor_name(signature, source: bytes) -> str | None:
311
+ identifiers = [child for child in signature.named_children if child.type == "identifier"]
312
+ return ".".join(_text(child, source).lstrip("$") for child in identifiers) or None
313
+
314
+
315
+ def _deep_named_child(node, types: set[str]):
316
+ if node is None:
317
+ return None
318
+ if node.type in types:
319
+ return node
320
+ for child in node.named_children:
321
+ found = _deep_named_child(child, types)
322
+ if found is not None:
323
+ return found
324
+ return None
325
+
326
+
327
+ def _is_closure(node, language: str) -> bool:
328
+ return node.type in {
329
+ "cpp": {"lambda_expression"}, "rust": {"closure_expression"},
330
+ "php": {"arrow_function", "anonymous_function"},
331
+ "swift": {"lambda_literal"}, "dart": {"function_expression", "lambda_expression"},
332
+ }.get(language, set())
333
+
334
+
335
+ def _assigned_name(node, language: str):
336
+ owner = _assigned_closure_owner(node, language)
337
+ if owner is None:
338
+ return None
339
+ candidates = {
340
+ "cpp": {"identifier"}, "rust": {"identifier"}, "php": {"variable_name"},
341
+ "swift": {"pattern"}, "dart": {"identifier"},
342
+ }[language]
343
+ return _deep_named_child(owner, candidates)
344
+
345
+
346
+ def _assigned_closure_owner(node, language: str):
347
+ if language == "php":
348
+ assignment = _ancestor(node, "assignment_expression")
349
+ if assignment is None or assignment.child_by_field_name("right") != node:
350
+ return None
351
+ return assignment.parent if assignment.parent and assignment.parent.type == "expression_statement" else assignment
352
+ owner_types = {
353
+ "cpp": {"declaration"}, "rust": {"let_declaration"},
354
+ "swift": {"property_declaration"}, "dart": {"local_variable_declaration"},
355
+ }[language]
356
+ current = node.parent
357
+ while current and current.type not in CALLABLE_TYPES[language]:
358
+ if current.type in owner_types:
359
+ return current
360
+ current = current.parent
361
+ return None
362
+
363
+
364
+ def _package_or_namespace(node, language: str, source: bytes) -> str:
365
+ root = node
366
+ while root.parent:
367
+ root = root.parent
368
+ types = {"go": {"package_clause"}, "kotlin": {"package_header"}, "csharp": {"file_scoped_namespace_declaration"}, "java": {"package_declaration"}}.get(language, set())
369
+ for child in root.named_children:
370
+ if child.type in types:
371
+ return _text(child, source).replace("package", "", 1).replace("namespace", "", 1).strip().rstrip(";")
372
+ return ""
373
+
374
+
375
+ def _javascript_lexical_name(node, source: bytes) -> str | None:
376
+ name = node.child_by_field_name("name")
377
+ if name:
378
+ return _text(name, source)
379
+ declarator = _ancestor(node, "variable_declarator")
380
+ if declarator and declarator.child_by_field_name("value") == node:
381
+ target = declarator.child_by_field_name("name")
382
+ if target and target.type in {"identifier", "property_identifier"}:
383
+ return _text(target, source)
384
+ return None
385
+
386
+
387
+ def _callback_name(node, region: ExecutableRegion) -> str:
388
+ row, column = node.start_point
389
+ point = region.original_point(row, column)
390
+ return f"<callback@{point.line}:{point.byte_column}>"
391
+
392
+
393
+ MAINSTREAM_LAMBDA_TYPES = {
394
+ "python": {"lambda"}, "go": {"func_literal"},
395
+ "kotlin": {"lambda_literal", "anonymous_function"},
396
+ "csharp": {"lambda_expression", "anonymous_method_expression"},
397
+ "java": {"lambda_expression"},
398
+ }
399
+
400
+
401
+ def _mainstream_lambda_identity(node, region: ExecutableRegion) -> str:
402
+ parts = [_callback_name(node, region)]
403
+ for current in _ancestors(node):
404
+ if current.type in MAINSTREAM_LAMBDA_TYPES[region.language]:
405
+ parts.append(_callback_name(current, region))
406
+ elif current.type in CALLABLE_TYPES[region.language]:
407
+ name = _name_node(current, region.language)
408
+ if name is not None:
409
+ parts.append(_text(name, region.source))
410
+ if region.language == "go" and current.type == "method_declaration":
411
+ receiver_type = _go_receiver_type(current, region.source)
412
+ if receiver_type:
413
+ parts.append(receiver_type)
414
+ elif current.type in {"class_definition", "class_declaration", "object_declaration", "struct_declaration", "record_declaration", "enum_declaration"}:
415
+ name = _name_node(current, region.language)
416
+ if name is not None:
417
+ parts.append(_text(name, region.source))
418
+ parts.append(region.original_path.stem if region.language == "python" else _package_or_namespace(node, region.language, region.source))
419
+ return ".".join(reversed([part for part in parts if part]))
420
+
421
+
422
+ def _is_anonymous_callable(node, region: ExecutableRegion) -> bool:
423
+ if region.language in {"javascript", "typescript", "tsx"}:
424
+ return _javascript_lexical_name(node, region.source) is None
425
+ if node.type in MAINSTREAM_LAMBDA_TYPES.get(region.language, set()):
426
+ return True
427
+ return _is_closure(node, region.language) and _second_wave_name(node, region.language, region.source) is None
428
+
429
+
430
+ def _object_assignment_name(node, source: bytes) -> str | None:
431
+ object_node = _ancestor(node, "object")
432
+ declarator = _ancestor(object_node, "variable_declarator") if object_node else None
433
+ target = declarator.child_by_field_name("name") if declarator else None
434
+ return _text(target, source) if target and target.type == "identifier" else None
435
+
436
+
437
+ def _ancestor(node, node_type: str):
438
+ current = node.parent if node else None
439
+ while current:
440
+ if current.type == node_type:
441
+ return current
442
+ current = current.parent
443
+ return None
444
+
445
+
446
+ def _ancestors(node) -> Iterator:
447
+ current = node.parent
448
+ while current:
449
+ yield current
450
+ current = current.parent
451
+
452
+
453
+ def _text(node, source: bytes) -> str:
454
+ return source[node.start_byte:node.end_byte].decode("utf-8")
455
+
456
+
457
+ def _is_else_if(node, language: str) -> bool:
458
+ if node.type not in {"if_statement", "if_expression"}:
459
+ return False
460
+ parent = node.parent
461
+ if language == "kotlin":
462
+ return bool(parent and parent.type == "control_structure_body" and parent.parent and parent.parent.type == "if_expression")
463
+ if language in {"javascript", "typescript", "tsx"}:
464
+ return bool(parent and parent.type == "else_clause")
465
+ if language == "php":
466
+ return bool(parent and parent.type == "else_if_clause")
467
+ if language == "swift":
468
+ return bool(parent and parent.type == "if_statement")
469
+ if language == "rust":
470
+ return bool(parent and parent.type == "else_clause")
471
+ return bool(parent and parent.type == "if_statement" and parent.child_by_field_name("alternative") == node)
472
+
473
+
474
+ def _is_default_branch(node, source: bytes, language: str | None = None) -> bool:
475
+ if node.type == "else_if_clause":
476
+ return False
477
+ if language == "python" and node.type == "case_clause" and node.child_by_field_name("guard") is not None:
478
+ return False
479
+ return _text(node, source).lstrip().startswith(("default", "else", "case _", "case var _", "_ ->", "_ =>"))
480
+
481
+
482
+ def _control_category(provider_kind: str, language: str) -> str:
483
+ if language == "swift" and provider_kind == "do_statement":
484
+ return "exception"
485
+ return CONTROL_CATEGORIES.get(provider_kind, provider_kind)
486
+
487
+
488
+ def _is_meaningful_control(node, language: str) -> bool:
489
+ if language == "swift" and node.type == "do_statement":
490
+ return any(child.type == "catch_block" for child in node.named_children)
491
+ return True
492
+
493
+
494
+ def _extra_switch_arm_ranges(node, language: str, region: ExecutableRegion) -> tuple[tuple[str, SourceRange], ...]:
495
+ if language == "java" and node.type == "switch_rule":
496
+ return () if _is_default_branch(node, region.source) else ((node.type, region.original_range(node)),)
497
+
498
+ clauses: tuple
499
+ provider_kind: str
500
+ if language == "cpp" and node.type == "compound_statement" and node.parent.type == "switch_statement":
501
+ clauses = tuple(_cpp_switch_clauses(node))
502
+ provider_kind = "case_statement"
503
+ elif language == "csharp" and node.type == "switch_body":
504
+ clauses = tuple(child for child in node.named_children if child.type == "switch_section")
505
+ provider_kind = "switch_section"
506
+ elif language == "java" and node.type == "switch_block":
507
+ clauses = tuple(child for child in node.named_children if child.type == "switch_block_statement_group")
508
+ provider_kind = "switch_block_statement_group"
509
+ elif language in {"javascript", "typescript", "tsx"} and node.type == "switch_body":
510
+ clauses = tuple(child for child in node.named_children if child.type in {"switch_case", "switch_default"})
511
+ provider_kind = "switch_case"
512
+ else:
513
+ return ()
514
+
515
+ ranges: list[SourceRange] = []
516
+ pending_case = None
517
+ for index, clause in enumerate(clauses):
518
+ non_default = not _is_default_branch(clause, region.source)
519
+ next_clause = clauses[index + 1] if language == "cpp" and index + 1 < len(clauses) else None
520
+ if not _classic_switch_clause_has_body(clause, next_clause):
521
+ if non_default and pending_case is None:
522
+ pending_case = clause
523
+ continue
524
+
525
+ representative = pending_case or (clause if non_default else None)
526
+ if representative is not None:
527
+ ranges.append(region.original_range(representative))
528
+ pending_case = None
529
+ return tuple((provider_kind, arm_range) for arm_range in ranges)
530
+
531
+
532
+ def _cpp_switch_clauses(node) -> Iterator:
533
+ for child in node.named_children:
534
+ if child.type == "switch_statement":
535
+ continue
536
+ if child.type == "case_statement":
537
+ yield child
538
+ yield from _cpp_switch_clauses(child)
539
+
540
+
541
+ def _classic_switch_clause_has_body(clause, next_clause=None) -> bool:
542
+ colon = next((child for child in clause.children if child.type == ":"), None)
543
+ if colon is None:
544
+ return False
545
+ for child in clause.children:
546
+ if not child.is_named or child.start_byte < colon.end_byte or child.type in {"comment", "empty_statement"}:
547
+ continue
548
+ if next_clause is not None and child.start_byte < next_clause.start_byte < child.end_byte:
549
+ if child.type == "compound_statement" or child.type.startswith("preproc_"):
550
+ return _cpp_wrapper_has_executable_before(child, next_clause.start_byte)
551
+ return True
552
+ return True
553
+ return False
554
+
555
+
556
+ def _cpp_wrapper_has_executable_before(node, limit: int) -> bool:
557
+ preprocessor_wrapper = node.type.startswith("preproc_")
558
+ for child in node.named_children:
559
+ if child.start_byte >= limit or child.type in {"case_statement", "comment", "empty_statement"}:
560
+ continue
561
+ if child.type == "compound_statement" or child.type.startswith("preproc_"):
562
+ if _cpp_wrapper_has_executable_before(child, limit):
563
+ return True
564
+ elif not preprocessor_wrapper or child.type.endswith(("_statement", "_declaration")) or child.type == "declaration":
565
+ return True
566
+ return False
567
+
568
+
569
+ def _php_switch_arm_ranges(node, language: str, region: ExecutableRegion) -> tuple[SourceRange, ...]:
570
+ """Normalize PHP case-label groups into executable non-default arms."""
571
+ if language != "php" or node.type != "switch_block":
572
+ return ()
573
+
574
+ ranges: list[SourceRange] = []
575
+ pending_case = None
576
+ for clause in (child for child in node.named_children
577
+ if child.type in {"case_statement", "default_statement"}):
578
+ value = clause.child_by_field_name("value")
579
+ body = [child for child in clause.named_children
580
+ if child != value and child.type not in {"comment", "empty_statement"}]
581
+ if not body:
582
+ if clause.type == "case_statement" and pending_case is None:
583
+ pending_case = clause
584
+ continue
585
+
586
+ representative = pending_case or (clause if clause.type == "case_statement" else None)
587
+ if representative is not None:
588
+ ranges.append(region.original_range(representative))
589
+ pending_case = None
590
+ return tuple(ranges)
591
+
592
+
593
+ def _pattern_guard(node, language: str):
594
+ if language == "python" and node.type == "case_clause":
595
+ guard = node.child_by_field_name("guard")
596
+ return guard.named_children[0] if guard is not None and guard.named_children else None
597
+ if language == "csharp" and node.type == "switch_expression_arm":
598
+ clause = next((child for child in node.named_children if child.type == "when_clause"), None)
599
+ return clause.named_children[0] if clause is not None and clause.named_children else None
600
+ if language == "rust" and node.type == "match_arm":
601
+ pattern = node.child_by_field_name("pattern")
602
+ return pattern.child_by_field_name("condition") if pattern is not None else None
603
+ if language == "swift" and node.type == "switch_entry":
604
+ children = node.named_children
605
+ for index, child in enumerate(children):
606
+ if child.type == "where_keyword" and index + 1 < len(children):
607
+ return children[index + 1]
608
+ return None
@@ -0,0 +1,13 @@
1
+ """Deterministic failures raised by the production syntax pipeline."""
2
+
3
+
4
+ class AnalysisError(RuntimeError):
5
+ """Base error suitable for Code Guard's existing exit-3 boundary."""
6
+
7
+
8
+ class ProviderUnavailableError(AnalysisError):
9
+ """The configured parser provider or a required grammar is unavailable."""
10
+
11
+
12
+ class SyntaxAnalysisError(AnalysisError):
13
+ """A supported source/container cannot produce authoritative facts."""